动机
一旦我完成了我的前两篇文章,我意识到通过CockroachDB支持的FerretDB代理MongoDB集合有很多可能性。CockroachDB具有独特的数据注册功能,可通过多区域抽象,倒置和部分索引,计算列以及强大的一致性来实现。今天,我们将讨论MongoDB和CockroachDB中的独特约束。
高级步骤
● 启动 9 节点多区域集群(CockroachDB专用)
● 启动FerretDB (Docker)
● 唯一索引
● 考虑
● 结论
分步说明
启动 9 节点多区域集群(CockroachDB专用)
我将使用与上一篇文章中相同的CockroachDB专用集群。有关详细步骤,请参阅上一篇文章。您可以通过以下方式获得CockroachDB专用的30天https://www.cockroachlabs.com/docs/cockroachcloud/quickstart-trial-cluster.html
启动FerretDB (Docker)
我将使用上一篇文章中的相同撰写文件,但是,由于我们将讨论多区域,我将对撰写文件进行更改并讨论以下步骤。
唯一索引
FerretDB正在开发中,许多功能可能仍然不可用。在上一篇文章中,我迁移了一个MongoDB数据集,该数据集依赖于某些集合的唯一索引。在撰写本文时,我尚未能够验证 FerretDB 中的独特约束工作。我的结论是它还没有实现。它抑制了我们想要讨论的一些功能,我将演示如何利用CockroachDB进行全局强大的一致唯一索引。sample_mflix
还原集合(如果以前未执行此操作)sample_mflix.users
mongorestore --archive=sampledata.archive --nsInclude=sample_mflix.users --numInsertionWorkersPerCollection=100
复制
2022-09-12T16:29:24.266-0400 sample_mflix.users 28.9KB 2022-09-12T16:29:26.156-0400 sample_mflix.users 28.9KB 2022-09-12T16:29:26.156-0400 finished restoring sample_mflix.users (185 documents, 0 failures) 2022-09-12T16:29:26.200-0400 restoring indexes for collection sample_mflix.users from metadata 2022-09-12T16:29:26.200-0400 index: &idx.IndexDocument{Options:primitive.M{"name":"email_1", "unique":true, "v":2}, Key:primitive.D{primitive.E{Key:"email", Value:1}}, PartialFilterExpression:primitive.D(nil)} 2022-09-12T16:29:26.203-0400 185 document(s) restored successfully. 0 document(s) failed to restore.
复制
请注意,一个独特的约束与数据一起恢复,查看数据集,确实在字段上有一个唯一的约束。CockroachDB中的模式不知道约束:email
CREATE TABLE sample_mflix.users_5e7cc513 (
_jsonb JSONB NULL,
rowid INT8 NOT VISIBLE NOT NULL DEFAULT unique_rowid(),
CONSTRAINT users_5e7cc513_pkey PRIMARY KEY (rowid ASC)
);
复制
如果我们尝试再次还原此文件,则不会触发约束,实际上,它会附加到现有行。
最初我们恢复了185条记录,如果我们重新运行恢复,我们应该看到370条记录
2022-09-12T16:38:01.207-0400 sample_mflix.users 28.9KB 2022-09-12T16:38:01.207-0400 finished restoring sample_mflix.users (185 documents, 0 failures) 2022-09-12T16:38:01.247-0400 restoring indexes for collection sample_mflix.users from metadata 2022-09-12T16:38:01.247-0400 index: &idx.IndexDocument{Options:primitive.M{"name":"email_1", "unique":true, "v":2}, Key:primitive.D{primitive.E{Key:"email", Value:1}}, PartialFilterExpression:primitive.D(nil)} 2022-09-12T16:38:01.251-0400 185 document(s) restored successfully. 0 document(s) failed to restore.
复制
ferretdb=> select count(*) from sample_mflix.users_5e7cc513 ;
count
-------
370
复制
很明显,唯一约束没有强制执行。FerretDB显示的内容是什么:
sample_mflix> db.getCollectionInfos( ) [ { name: 'users', type: 'collection' } ]
复制
sample_mflix> db.users.getIndexes() MongoServerError: no such command: 'listIndexes'
复制
让我们尝试显式添加它
sample_mflix> db.users.createIndex( { "email": 1 }, { unique: true } ) email_1
复制
让我们尝试使用FerretDB插入一行。
sample_mflix> db.users.findOne({ }) { _id: ObjectId("59b99db4cfa9a34dcd7885b6"), name: 'Ned Stark', email: 'sean_bean@gameofthron.es', password: '$2b$12$UREFwsRUoyF0CRqGNK0LzO0HM/jLhgUCNNIJ9RJAqMUQ74crlJ1Vu' }
复制
我们将添加另一条具有相同电子邮件地址的记录
sample_mflix> db.users.insertOne({name: 'Stark Sr.', email: 'sean_bean@gameofthron.es' }) { acknowledged: true, insertedId: ObjectId("631f99c3cc8ad2b643a9f459") }
复制
已接受插入
我将给MongoDB带来的好处是,他们怀疑他们实现独特约束确实有效。我认为这里的问题出在费雷特DB身上。
让我们在CockroachDB端强制执行约束:
我们需要在 JSONB 字段中的电子邮件字段上创建一个计算列,我们还将约束指定为命令的一部分。
UNIQUE
ALTER TABLE sample_mflix.users_5e7cc513 ADD COLUMN email STRING NOT NULL UNIQUE AS ((_jsonb->>'email')::STRING) VIRTUAL;
复制
如果您已经添加了Ned Stark的电子邮件地址或按照我建议的两次恢复了用户的集合,CockroachDB将检测到唯一的约束违规。
ERROR: failed to ingest index entries during backfill: duplicate key value violates unique constraint "users_5e7cc513_email_key" DETAIL: Key (email)=('sean_bean@gameofthron.es') already exists.
复制
您可以选择截断表或手动删除所有冲突。
LE sample_mflix.users_5e7cc513 ;
ALTER TABLE sample_mflix.users_5e7cc513 ADD COLUMN email STRING NOT NULL UNIQUE AS ((_jsonb->>'email')::STRING) VIRTUAL;
复制
架构现在如下所示:
CREATE TABLE sample_mflix.users_5e7cc513 (
_jsonb JSONB NULL,
rowid INT8 NOT VISIBLE NOT NULL DEFAULT unique_rowid(),
email STRING NOT NULL AS (_jsonb->>'email':::STRING) VIRTUAL,
CONSTRAINT users_5e7cc513_pkey PRIMARY KEY (rowid ASC),
UNIQUE INDEX users_5e7cc513_email_key (email ASC)
) LOCALITY REGIONAL BY TABLE IN PRIMARY REGION;
复制
此时,我们可以尝试再次恢复集合,这一次,CockroachDB将在现场强制执行唯一性。
email
2022-09-14T12:43:59.689-0400 finished restoring sample_mflix.users (185 documents, 0 failures) 2022-09-14T12:43:59.715-0400 restoring indexes for collection sample_mflix.users from metadata 2022-09-14T12:43:59.715-0400 index: &idx.IndexDocument{Options:primitive.M{"name":"email_1", "unique":true, "v":2}, Key:primitive.D{primitive.E{Key:"email", Value:1}}, PartialFilterExpression:primitive.D(nil)} 2022-09-14T12:43:59.718-0400 185 document(s) restored successfully. 0 document(s) failed to restore.
复制
让我们尝试使用插入记录mongosh
db.users.insertOne({name: 'Stark Sr.', email: 'sean_bean@gameofthron.es' })
复制
MongoServerError: [pool.go:283 pgdb.(*Pool).InTransaction] [msg_insert.go:108 pg.(*Handler).insert.func1] [insert.go:57 pgdb.InsertDocument] ERROR: duplicate key value violates unique constraint "users_5e7cc513_email_key" (SQLSTATE 23505)
复制
我们有违规行为!但为了100%确定,让我们尝试再次恢复桌子
2022-09-12T16:56:55.884-0400 Failed: sample_mflix.users: error restoring from archive 'sampledata.archive': (InternalError) [pool.go:283 pgdb.(*Pool).InTransaction] [msg_insert.go:108 pg.(*Handler).insert.func1] [insert.go:57 pgdb.InsertDocument] ERROR: duplicate key value violates unique constraint "users_5e7cc513_email_key" (SQLSTATE 23505)
2022-09-12T16:56:55.884-0400 0 document(s) restored successfully. 0 document(s) failed to restore.
复制
考虑
我们可以就此打住,但我想让这个概念坚持下去,因为拥有一个具有强大全球一致性的多区域数据库有很大的好处。唯一约束在创建后立即在所有区域、可用区和节点上强制执行。如果我们从 比如说 访问数据库,我们会得到同样的违规行为。让我们拆下 FerretDB 组合环境并创建三个新客户端,每个客户端访问区域 (, , ) 集群终端节点。aws-us-east-2 mongosh aws-us-east-1 aws-us-east-2 aws-us-west-2
version: "3"
services:
ferretdb-us-east-1:
image: ghcr.io/ferretdb/ferretdb:latest
hostname: 'ferretdb-us-east-1'
container_name: 'ferretdb-us-east-1'
restart: 'on-failure'
command:
[
'-listen-addr=:27017',
## Dedicated multiregion cluster
'-postgresql-url=postgresql://artem:password@artem-mr-7xw.aws-us-east-1.cockroachlabs.cloud:26257/ferretdb?sslmode=verify-full&sslrootcert=/certs/artem-mr-ca.crt'
]
ports:
- 27017:27017
volumes:
- /Users/artem/.postgresql/root.crt:/certs/root.crt
- /Users/artem/Library/CockroachCloud/certs/artem-mr-ca.crt:/certs/artem-mr-ca.crt
ferretdb-us-east-2:
image: ghcr.io/ferretdb/ferretdb:latest
hostname: 'ferretdb-us-east-2'
container_name: 'ferretdb-us-east-2'
restart: 'on-failure'
command:
[
'-listen-addr=:27017',
## Dedicated multiregion cluster
'-postgresql-url=postgresql://artem:password@artem-mr-7xw.aws-us-east-2.cockroachlabs.cloud:26257/ferretdb?sslmode=verify-full&sslrootcert=/certs/artem-mr-ca.crt'
]
ports:
- 27019:27017
volumes:
- /Users/artem/.postgresql/root.crt:/certs/root.crt
- /Users/artem/Library/CockroachCloud/certs/artem-mr-ca.crt:/certs/artem-mr-ca.crt
ferretdb-us-west-2:
image: ghcr.io/ferretdb/ferretdb:latest
hostname: 'ferretdb-us-west-2'
container_name: 'ferretdb-us-west-2'
restart: 'on-failure'
command:
[
'-listen-addr=:27017',
## Dedicated multiregion cluster
'-postgresql-url=postgresql://artem:password@artem-mr-7xw.aws-us-west-2.cockroachlabs.cloud:26257/ferretdb?sslmode=verify-full&sslrootcert=/certs/artem-mr-ca.crt'
]
ports:
- 27021:27017
volumes:
- /Users/artem/.postgresql/root.crt:/certs/root.crt
- /Users/artem/Library/CockroachCloud/certs/artem-mr-ca.crt:/certs/artem-mr-ca.crt
复制
我已经创建了服务 ,并且.连接字符串与区域终结点一致,分别公开 上的主机端口。请随意验证每个服务的 。ferretdb-us-east-1 ferretdb-us-east-2 ferretdb-us-west-2 27017 27019
27021-postgresql-url
docker compose up -d docker compose ps
复制
NAME COMMAND SERVICE STATUS PORTS ferretdb-us-east-1 "/ferretdb -listen-a…" ferretdb-us-east-1 running 0.0.0.0:27017->27017/tcp ferretdb-us-east-2 "/ferretdb -listen-a…" ferretdb-us-east-2 running 0.0.0.0:27019->27017/tcp ferretdb-us-west-2 "/ferretdb -listen-a…" ferretdb-us-west-2 running 0.0.0.0:27021->27017/tcp
复制
我对Mongo CLI不够熟悉,无法证明每个端点都在访问区域实例,我很快就会在SQL中展示它。同时,让我们尝试插入一条记录
访问指向端点的点mongosh aws-us-west-2
mongosh mongodb://localhost:27021
复制
插入记录
sample_mflix> db.users.insertOne({name: 'Stark Sr.', email: 'sean_bean@gameofthron.es' })
MongoServerError: [pool.go:283 pgdb.(*Pool).InTransaction] [msg_insert.go:108 pg.(*Handler).insert.func1] [insert.go:57 pgdb.InsertDocument] ERROR: duplicate key value violates unique constraint "users_5e7cc513_email_key" (SQLSTATE 23505)
复制
它失败了,并证明了CockroachDB索引在各个地区都是一致的。你可能会问,我为什么要如此努力地表达这一点。我正在阅读MongoDB文档,并发现了以下https://www.mongodb.com/docs/manual/core/index-unique/#building-unique-index-on-replica-sets-and-sharded-clusters:
对于副本集和分片集群,使用滚动过程创建唯一索引要求您在此过程中停止对集合的所有写入。如果在此过程中无法停止对集合的所有写入,请不要使用滚动过程。相反,请通过以下方式在集合上构建唯一索引:
在副本集的主数据库上发出
db.collection.createIndex(),
或者
正在为分片集群的 mongos 上发布
db.collection.createIndex()
。
如果我误解了注释,请纠正我,但它说当你在MongoDB中创建索引时,你必须停止写入集合。此外,如果停止写入不是一个选项,则必须创建一个索引,据我所知,该索引是Mongo中的追随者副本。CockroachDB绝对不是这种情况,并且违背了CockroachDB的一致性保证。唯一索引是全局的,并且在所有节点、可用区和区域之间保持一致。您只需创建一次,就不会停止对表的写入。也就是说,让我在SQL中向您展示此示例。mongos
让我连接到该区域并故意尝试违反约束aws-us-west-2
cockroach sql --url "postgresql://artem:password@artem-mr-7xw.aws-us-west-2.cockroachlabs.cloud:26257/ferretdb2?sslmode=verify-full&sslrootcert=$HOME/Library/CockroachCloud/certs/artem-mr-ca.crt"
复制
让我们确认一下该地区
select gateway_region();
复制
gateway_region
----------------
aws-us-west-2
复制
让我们看看我的主机的网络往返时间
select 1;
复制
Time: 69ms total (execution 1ms / network 68ms)
复制
这似乎是准确的,从我在新泽西州的地方有大约70ms的RTT。
让我们尝试插入一条记录
INSERT INTO sample_mflix.users_5e7cc513 ("_jsonb") VALUES (
'{"email": "sean_bean@gameofthron.es"}'
);
复制
ERROR: duplicate key value violates unique constraint "users_5e7cc513_email_key" SQLSTATE: 23505 DETAIL: Key (email)=('sean_bean@gameofthron.es') already exists. CONSTRAINT: users_5e7cc513_email_key
复制
结论
这总结了我们对MongoDB集合的独特约束实验。我承认有些功能尚未在MongoDB端进行测试,因为我们是通过FerretDB代理的。但是,我希望我已经为您提供了足够的证据,即具有严格架构验证的适当关系数据库可以保护用户免受数据质量问题的影响。我期待着FerretDB的新实验!
原文标题:Experimenting With Unique Constraints in CockroachDB, MongoDB, and FerretDB
原文作者: Artem Ervits
原文地址:https://dzone.com/articles/experimenting-with-unique-constraints-in-cockroach