长沙专业做网站公司有哪些,网站建设运营的灵魂是什么意思,百度网站推广网络,亚网互联网站设计启动新项目时需要做出的决定之一是使用哪个数据库。如果您使用的是Django这样的包含电池的框架#xff0c;那么没有理由再三考虑。选择一个受支持的数据库引擎#xff0c;就可以了。另一方面#xff0c;如果你使用像FastAPI或Flask这样的微框架#xff0c;你需要自己做出这…启动新项目时需要做出的决定之一是使用哪个数据库。如果您使用的是Django这样的包含电池的框架那么没有理由再三考虑。选择一个受支持的数据库引擎就可以了。另一方面如果你使用像FastAPI或Flask这样的微框架你需要自己做出这个决定。您的决策会影响您需要哪些库以及如何进行数据建模。虽然你可能只是接触Postgres但还有其他选择。其中之一是DynamoDB。与Postgres一样它也是Python项目的绝佳选择。
在本文中我将比较Postgres和DynamoDB。我将描述如何使用它们解释它们的区别并帮助您决定为Python应用程序选择哪一个。
什么是Postgres什么是DynamoDB
Postgres是一个关系数据库管理系统RDBMS。这是一个开源项目已经存在了35年多。它是一个成熟稳定的数据库引擎。对于大多数项目来说这是一个不错的选择。
DynamoDB是AWS提供的NoSQL数据库服务。这是一项完全管理的服务这意味着您不需要担心扩展、备份或可用性。对于需要快速扩展并且不想花时间管理数据库的项目来说这是一个很好的选择。
第一个明显的区别是Postgres是SQL数据库而DynamoDB是NoSQL数据库。这并不能告诉我们什么。是的DynamoDB不是SQL数据库但它是什么DynamoDB实际上是一个键/值数据库。这对你的申请意味着什么我们怎么能比较这两个数据库呢密钥/值存储真的不就是Redis吗我们现在还不要妄下结论。让我们先来看一个例子。
比如说我们需要管理TODO任务。一种经典的SQL方法是创建这样一个表 然后我们可以查询它以获取给定所有者的所有任务
SELECT * FROM tasks WHERE owner johndoe.com;DynamoDB的情况有所不同。DynamoDB是一个密钥/值存储。我们可以在两个关键模式之间进行选择。我们只能使用HASH密钥。在这种情况下我们需要知道ID才能查询记录可以将其视为Redis。使用这个密钥模式我们会有一个这样的表 我们可以查询它以获取给定ID的任务
import boto3dynamodb boto3.resource(dynamodb)
table dynamodb.Table(tasks)table.get_item(Key{ID: some_id,},
)
因此没有办法有效地列出给定所有者的任务。 实际上您可以使用扫描来扫描整个表但这是非常低效的应该避免。 幸运的是还有另一个选项HASHRANGE密钥模式。在这种情况下我们可以使用两个键来识别一个记录。我们可以使用HASH键来标识分区使用RANGE键来标识该分区内的记录。HASH和RANGE键组合对于每个记录都必须是唯一的。在我们的情况下我们可以将owner用作HASH密钥将ID用作RANGE密钥。在这种情况下我们会有这样的表格 有了这样的结构我们可以查询给定所有者的所有任务如下所示
import boto3
from boto3.dynamodb.conditions import Keydynamodb boto3.resource(dynamodb)
table dynamodb.Table(tasks)table.query(KeyConditionExpressionKey(Owner).eq(johndoe.com),
)现在您将获得所有者为 johndoe.com 的所有记录这些记录将按ID排序。 尽管我们只是触及了表面但我想指出一点您可以使用Postgres或DynamoDB作为您的应用程序数据库。您只需要以不同的方式处理数据建模。
ORMs
ORM对象关系映射器是允许您轻松地将数据库记录映射到Python对象的库。它们大多遵循活动记录或数据映射器模式。使用活动记录模式模型对象负责业务逻辑和数据持久性。Django ORM就是这样一个例子。对于数据映射器这两个职责被委派给两个对象模型对象包含属性和业务逻辑而映射器对象负责数据持久性。一个例子是SQLAlchemy。它们构建在SQL数据库之上。因此Postgres有很多ORM选项可供选择也就不足为奇了——SQLAlchemy、Django ORM、Peewee、PonyORM等等。
选择哪一个主要取决于个人喜好。虽然语法可能不同但它们都允许您轻松定义模型和查询数据库。让我们来看看使用SQLAlchemy的示例
from sqlalchemy import Column, Integer, String, create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmakerBase declarative_base()class Task(Base):__tablename__ tasksid Column(Integer, primary_keyTrue, autoincrementTrue)title Column(String)status Column(String)owner Column(String)engine create_engine(postgresql://postgres:postgreslocalhost:5432/postgres)
Base.metadata.create_all(engine)Session sessionmaker(bindengine)
session Session()task Task(titleClean your office, statusOPEN, ownerjohndoe.com)
session.add(task)
session.commit()tasks session.query(Task).filter(Task.owner johndoe.com).all()
print(tasks)正如您所看到的这非常简单定义模型创建会话然后查询数据库。您可以对其他ORM使用相同/相似的方法。
虽然在Postgres中使用ORM时情况会非常好但我不能对DynamoDB说同样的话。DynamoDB有一些ORM例如PynamoDB和DynamORM但在我看来它们破坏了DynamoDB的整个目的。如果你想真正从DynamoDB中受益你应该采用单表设计——ORM不支持这种设计。我所说的“真正的好处”是指实际使用DynamoDB而不是在其上构建另一个带有UUID的关系数据库。让我们来看看如何使用它。 等效于NoSQL数据库的ORM是ODM对象文档映射器。例如请参阅MongoDB的ODM。与ORM一样它们简化了与数据库的交互。您也可以为DynamoDB找到类似的解决方案例如DynamoDB mapper。深入了解后您会意识到它们没有得到广泛使用没有得到维护没有做好生产准备或者它们强制执行了不必要的约束。 您可以看到以下内容
HASH键有一个通用名称 PK 分区键。它可以用于不同类型的记录。RANGE键有一个通用名称 SK 排序键。它可以用于不同类型的记录。
我们保留了其余的内容所以我们可以很容易地将数据映射回模型对象。由于DynamoDB是无模式的所以我们只需要为每个记录提供PK和SK。其他一切都是可选的。这使我们能够对不同类型的记录使用一个表。 为了支持更复杂的示例和查询需要对PK和SK使用复合密钥。例如任务和板的密钥结构不同 Tasks: PK#TASK#{owner}, SK#TASK#{task_id}. Boards: PK#BOARD#{owner}, SK#BOARD#{contact_id}. 这使您能够按所有者轻松查询所有任务或按所有者查询所有板。ORM不鼓励使用单表设计所必需的复合键。这也是ORM在DynamoDB中表现不佳的原因之一。你可以阅读这篇有趣的文章来了解更多。 DynamoDB注重可预测的性能。这就是为什么我们只能通过HASH和RANGE键查询记录HASH键必须始终存在于查询中。 无法按其他列进行查询您可以使用索引来支持更多的查询但稍后会详细介绍。 因此我们必须围绕查询而不是数据构建数据模型。这也适用于建立关系模型。使用ORM您只需定义两个模型之间的外键关系。然后您将能够通过其父对象访问相关对象反之亦然。没有必要做任何其他事情。虽然这对于简单的应用程序来说可能很方便但它可能会成为应用程序中的一个巨大瓶颈N1查询。使用DynamoDB单表设计希望通过一个查询加载所需的所有内容。
例如如果你想加载一个包含所有任务的板你必须对DynamoDB进行单个查询。它将返回不同类型的记录板名称、上次更新…、打开的任务、关闭的任务等等。然后由您将这些内容映射到包含任务等的板对象。因此几乎不可能为DynamoDB构建通用ORM。所有内容都非常特定于用例/查询。虽然这听起来像是一项艰巨的工作但它迫使你进行高效的设计。因此使用DynamoDB您需要为数据持久性构建自己的抽象——例如存储库。您可以指定您的纯Python或pydantic或其他模型并使用Boto3来实现您的存储库。
Models:
from dataclasses import dataclass
from enum import Enum
from uuid import UUIDclass TaskStatus(str, Enum):OPEN OPENCLOSED CLOSEDdataclass
class Task:id: UUIDtitle: strstatus: TaskStatusowner: strclassmethoddef create(cls, id_, title, owner):return cls(id_, title, TaskStatus.OPEN, owner)Store:
from uuid import UUID import boto3
from boto3.dynamodb.conditions import Keyfrom models import Task, TaskStatusclass TaskStore:def __init__(self, table_name):self.table_name table_namedef add(self, task):dynamodb boto3.resource(dynamodb)table dynamodb.Table(self.table_name)table.put_item(Item{PK: f#TASK#{task.owner},SK: f#TASK#{task.id},id: str(task.id),title: task.title,status: task.status.value,owner: task.owner,})def list_by_owner(self, owner):dynamodb boto3.resource(dynamodb)table dynamodb.Table(self.table_name)last_key Nonequery_kwargs {IndexName: GS1,KeyConditionExpression: Key(PK).eq(f#TASK#{owner}),}tasks []while True:if last_key is not None:query_kwargs[ExclusiveStartKey] last_keyresponse table.query(**query_kwargs)tasks.extend([Task(idUUID(record[id]),titlerecord[title],ownerrecord[owner],statusTaskStatus[record[status]],)for record in response[Items]])last_key response.get(LastEvaluatedKey)if last_key is None:breakreturn tasks简而言之Postgres和DynamoDB都有ORM。您可能想在Postgres中使用一个但可能不想在DynamoDB中使用。
Migrations
另一个有趣的主题–数据库迁移。数据库迁移提供了一种更新数据库模式的方法。对于Postgres有一些工具可以简化迁移
AlembicDjango migrations
您可以使用它们轻松地将表/索引/列/约束添加到数据库中。它们甚至可以通过ORM从模型定义中自动生成。Alembic迁移示例
create tasks tableRevision ID: 1f8b5f5d1f5a
Revises:
Create Date: 2021-12-05 12:00:00.000000from alembic import op
import sqlalchemy as sa# revision identifiers, used by Alembic.
revision 1f8b5f5d1f5a
down_revision None
branch_labels None
depends_on Nonedef upgrade():op.create_table(tasks,sa.Column(id, sa.Integer, primary_keyTrue, autoincrementTrue),sa.Column(title, sa.String),sa.Column(status, sa.String),sa.Column(owner, sa.String),)def downgrade():op.drop_table(tasks)对于大多数常见的操作它非常简单。对于自动生成的迁移没有太多工作要做。如果需要您可以随时手动编写。
对于DynamoDB没有模式——我们也说过我们想要一个表。无法更改密钥因此无法迁移。还是有事实证明在向记录添加/从记录中删除属性时您有两个选项
在运行时处理更改–例如添加具有默认值的列遵循以下三步模式 1 开始使用新的“属性”写入新记录 2 使用新的“属性”将现有记录更新为所需值您可以扫描表中的所有记录并更新它们 3 开始在代码中使用新的“属性” 您可以将三步模式推广到其他类型的更改如删除属性。 有了所有可用的工具Postgres的迁移更容易。 如果您在更复杂的项目中工作而性能差或停机时间确实不是一种选择那么您可能会手动编写大部分迁移代码。对于这种情况Postgres和DynamoDB之间没有太大的区别。
Queries
事实上我们已经谈到了这个话题。我们甚至查看了示例查询。尽管如此让我们更深入地挖掘。
任何不习惯DynamoDB的人都会说由于查询数据的能力非常有限使用它没有意义。的确查询在DynamoDB中的灵活性要低得多。人们普遍认为在使用SQL数据库时可以使用所需的任意列集查询任何数据。理论上是这样的。换句话说只有当您的数据库足够小可以放入服务器的内存时这才是正确的。
一旦您有了更严重的数据量查询往往会变慢这反过来又会减慢应用程序的速度。迟早你需要开始考虑指数。要进行快速查询您需要添加索引并仅按索引列进行查询。您还将意识到如果不限制返回记录的数量您将无法进行查询。无限制的查询很容易导致系统瘫痪。这是因为你根本不知道他们会返回多少数据。您的数据库可能需要从磁盘读取10亿行这需要时间。
那我们该何去何从呢DynamoDB的构建方式迫使您提前考虑这些问题。
在设计数据模型时您需要
使用HASH键对数据进行分区使用RANGE键对数据进行排序
然后您只能通过HASH和RANGE键或预定义的索引进行查询这些索引有自己的HASH和RANGE键。单个查询返回的数据量也有限制。所以你不得不使用分页。
所有这些限制在一开始可能听起来很可怕让人不知所措。一旦你习惯了它们你就会意识到它们的存在是有原因的。让我们来看看示例查询。。。
With DynamoDB:
import boto3
from boto3.dynamodb.conditions import Keyowner johndoe.comdynamodb boto3.resource(dynamodb)
table dynamodb.Table(tasks-api)
last_key None
query_kwargs {IndexName: GS1,KeyConditionExpression: Key(PK).eq(f#TASK#{owner}),
}
tasks []while True:if last_key is not None:query_kwargs[ExclusiveStartKey] last_keyresponse table.query(**query_kwargs)tasks.extend(response[Items])last_key response.get(LastEvaluatedKey)if last_key is None:breakprint(tasks)使用PostgresSQLAlchemy分页查询
from models import Session, Tasktasks []
last_evaluated_id -1
session Session()while True:query session.query(Task).filter(Task.id last_evaluated_id).order_by(Task.id).limit(10)tasks.extend(query.all())last_evaluated_id tasks[-1].idif len(tasks) 10:breakprint(tasks)正如您所看到的DynamoDB和Postgres分页查询在代码复杂度上没有太大区别。
DynamoDB还有其他查询限制
我们必须始终至少提供HASH密钥我们只能通过HASH和RANGE键或预定义索引进行查询返回的记录按RANGE键排序。您只能在升序和降序之间进行选择
我们知道在当前的表设计中我们只能支持两个查询
按所有者列出您已在上面看到按所有者和任务ID获取您可以在下面看到
import boto3
from boto3.dynamodb.conditions import Keyowner johndoe.com
task_id 7b0f4bc8-4751-41f1-b3b1-23a935d81cd6dynamodb boto3.resource(dynamodb, endpoint_urlhttp://localhost:9999)
table dynamodb.Table(tasks-api)query_kwargs {KeyConditionExpression: Key(PK).eq(f#TASK#{owner}) Key(SK).eq(f#TASK#{task_id}),
}
task response table.query(**query_kwargs)[Items][0] print(task)例如我们不能按状态进行查询。但我们可以用Postgres做到这一点
from models import Session, Tasktasks []
last_evaluated_id -1
session Session()while True:query session.query(Task).filter(Task.status OPEN).filter(Task.id last_evaluated_id).order_by(Task.id).limit(10)tasks.extend(query.all())last_evaluated_id tasks[-1].idif len(tasks) 10:breakprint(tasks)别担心我们仍然可以在DynamoDB中支持这样的查询。我们只需要添加即可使用全局二级索引GSI。我们可以创建一个名为GS1的具有单个GSI的表如下所示
import boto3client boto3.client(dynamodb, endpoint_urlhttp://localhost:9999)
client.create_table(TableNametasks-api,KeySchema[{AttributeName: PK, KeyType: HASH},{AttributeName: SK, KeyType: RANGE},],AttributeDefinitions[{AttributeName: PK, AttributeType: S},{AttributeName: SK, AttributeType: S},{AttributeName: GS1PK, AttributeType: S},{AttributeName: GS1SK, AttributeType: S},],GlobalSecondaryIndexes[{IndexName: GS1,KeySchema: [{AttributeName: GS1PK, KeyType: HASH},{AttributeName: GS1SK, KeyType: RANGE},],Projection: {ProjectionType: ALL},},],BillingModePAY_PER_REQUEST,
)就像表格一样GSI有自己的HASH和RANGE键。然后我们可以按如下状态进行查询
import boto3
from boto3.dynamodb.conditions import Keystatus OPENdynamodb boto3.resource(dynamodb, endpoint_urlhttp://localhost:9999)
table dynamodb.Table(tasks-api)
last_key None
query_kwargs {KeyConditionExpression: Key(GS1PK).eq(f#TASK#{status}),IndexName: GS1,
}
tasks []while True:if last_key is not None:query_kwargs[ExclusiveStartKey] last_keyresponse table.query(**query_kwargs)tasks.extend(response[Items])last_key response.get(LastEvaluatedKey)if last_key is None:breakprint(tasks)正如您所看到的您需要显式定义查询时要使用的索引。另一方面Postgres自己做到了这一点。再一次如果您比较查询您会发现它们毕竟没有那么大的不同。
只要您使用的是精确的查询…Postgres和DynamoDB之间就没有太大区别。使用DynamoDB您无法进行LIKE、IN等查询或全文搜索。要支持这样的功能您需要将其与Elasticsearch或类似功能配对。 您可以在此处看到HASH/RANGE键支持的条件的完整列表。 简而言之当用作应用程序数据库时这两个数据库都应该支持您的需求。如果你想进行更复杂的查询例如报告、分析等你要么需要使用Postgres要么将DynamoDB与其他一些服务配对如Elasticsearch、Athena或Redshift等。
Performance
性能总是一个棘手的话题。进入“意见”领域是很容易的。我会尽量避免的。如果你在网上搜索你会发现有人赞扬DynamoDB诋毁Postgres反之亦然。事实是在大多数情况下你会看到以下缺陷之一
DynamoDB被用作关系数据库——多个表没有单表设计Postgres的索引/分区很差扫描与DynamoDB一起使用Postgres被非索引列或无限制查询
这些年来我在各种生产环境中都使用过这两种方法。我可以告诉你以下几点如果使用得当它们都会燃烧得很快。当直接比较时您可能会注意到它们之间的细微差异但从应用程序的角度来看您不会注意到太大的性能差异。当然在亚马逊/谷歌/奈飞等网站。规模你会明显地看到一些不同毕竟创建DynamoDB是有原因的。
长话短说对于大多数项目来说性能不应该是主要的决定因素。至少如果你不是亚马逊/网飞/。。。。相反把时间花在开发一个高效的数据访问模型上你会没事的。
Backups
备份是一个经常被遗忘的主题。没有备份之前一切都很好。由于我们已经在AWS和DynamoDB中让我们将AWS RDS上的Postgres与DynamoDB进行比较。
它们都支持按需备份和连续备份。它们都提供时间点恢复我强烈建议您这样做。我强烈建议您同时使用内置备份。对于时间点恢复两者的流程基本相同
从给定的时间戳创建一个时间点恢复这将创建一个新实例RDS或一个新表DynamoDB将已恢复数据中丢失的数据复制到应用程序实例/表或将应用程序重新路由到新实例/表 提示AWS上的备份符合SOC 2等证书。 您可以从DynamoDB和RDS的官方文档中了解更多信息。
Transactions
在我们结束之前让我们谈一谈事务。事务是一种允许我们将多个操作封装到单个数据库操作中的机制。这有助于确保要么执行所有操作要么不执行任何操作。例如通过这种方式我们可以确保减少库存项目的数量并创建新订单。如果没有交易我们可能会收到一个没有库存的订单——例如有人在你之前下了订单。
事务是SQL数据库的“面包和黄油”。另一方面它们在NoSQL数据库中并不常见。Postgres和DynamoDB都支持事务——要么全部支持要么什么都不支持。它们在Postgres中可能感觉更惯用但您也可以在DynamoDB中使用它们。您只需要使用较低级别的Boto3 API。
因此在这方面您应该可以使用DynamoDB或Postgres。
您可以从DynamoDB和Postgres的官方文档中了解更多信息。
我应该使用哪一个
所有这些都给你留下了一个问题我应该使用哪一个对于大多数情况您可以使用其中任何一种。尽管如此还是有一些指导原则可以帮助您进行选择
如果您正在使用Django或任何其他包含电池的框架请使用Postgres。这是最简单的方法。如果您尝试使用DynamoDB那么您只需要与框架作斗争。如果你有一个缺乏经验的团队去Postgres。这更容易让你的头脑清醒起来并在以后纠正你的错误。如果您需要在多个AWS区域轻松复制数据请使用DynamoDB。它就是为这个而建的。如果您的流量非常大请选择DynamoDB。您可以按要求付款。如果你想无服务器化那就选择DynamoDB。但在这种情况下您也需要在Lambda上运行您的应用程序。如果您不在AWS生态系统中请使用Postgres。DynamoDB与AWS紧密相连。
无论您选择哪种请记住拥有一个高效的数据模型比您使用的数据库更重要。DynamoDB会强迫你这么做。Postgres不会。但正如您所看到的当有效地使用它们时您最终会得到非常相似的使用模式。
结论
虽然本文涵盖了很多内容但仍有很多内容需要探索。那么接下来该怎么办呢你可以用它们两个构建同一个应用程序以真正掌握它们。我们——惊喜惊喜——有两门课程可以帮助你做到这一点
Serverless FastAPI courseScalable FastAPI course
前者使用DynamoDB后者使用Postgres。试着用两者构建相同的东西你会看到它的进展。如果没有别的我相信一旦完成你在数据建模方面会做得更好。
您还可以使用本文中的示例GitHub repository。