基因组变异查询看起来像一次普通的数据检索,真正进入临床和科研环境后,却要同时面对海量数据、突发流量、访问控制和成本约束。澳大利亚国家科学机构 CSIRO 构建的 Serverless Beacon(sBeacon),通过 Amazon S3、AWS Lambda、Amazon DynamoDB 和 Amazon Athena 实现 GA4GH Beacon 标准,为生产规模的基因组变异查询提供了一种无服务器路径。
这套设计值得关注的地方,不只是“没有服务器要维护”,而是把在线查询、对象存储和分析任务分给不同服务,避免让一个数据库承担所有工作。
把不同查询交给不同的数据层
GA4GH Beacon 的核心场景,是让调用方按照染色体位置、参考碱基和替代碱基等条件,询问某个数据集中是否存在对应变异。在真实系统中,查询通常可以分成两类:
- 低延迟、结构明确的在线查询:例如根据标准化后的变异键判断记录是否存在。
- 范围更大、条件更复杂的分析查询:例如研究人员对存放在对象存储中的数据执行聚合、过滤或队列分析。
结合 sBeacon 使用的 AWS 服务,可以把职责理解为:
- Amazon S3 保存大规模基因组变异数据和分析文件,适合承载 Parquet 等列式格式。
- AWS Lambda 负责校验 Beacon 请求、规范化变异参数、执行授权并组织响应。
- Amazon DynamoDB 支撑可预测键值上的低延迟读取,例如精确变异查询或元数据索引。
- Amazon Athena 直接查询 S3 数据,用于不适合建成在线键值索引的复杂分析。
这里的关键不是把每一份数据同时复制到所有服务,而是先识别访问模式:高频精确查询进入 DynamoDB,体量大但频率较低的分析留在 S3,并由 Athena 按需扫描。
这种拆分也直接影响成本。Lambda 按调用和执行资源计费,DynamoDB 可以使用按需容量,Athena 按扫描数据量计费,S3 则承担相对经济的持久化存储。系统不需要为了偶发峰值长期运行固定规模的计算集群。
一个可改造的最小查询服务
下面是一个简化的实践样例,用于展示如何把 Beacon 风格的精确变异查询映射到 Lambda 和 DynamoDB。它不是 sBeacon 的原始代码,也没有覆盖完整 GA4GH Beacon 规范;假设已经将变异规范化为以下键:
{referenceName}:{start}:{referenceBases}:{alternateBases}
运行前需要安装并配置 AWS CLI 与 AWS SAM CLI。创建如下目录:
beacon-demo/
├── template.yaml
└── src/
└── app.py
template.yaml:
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: Minimal serverless genomic variant lookup API
Globals:
Function:
Runtime: python3.12
Timeout: 10
MemorySize: 256
Resources:
BeaconApi:
Type: AWS::Serverless::HttpApi
VariantTable:
Type: AWS::DynamoDB::Table
Properties:
BillingMode: PAY_PER_REQUEST
SSESpecification:
SSEEnabled: true
AttributeDefinitions:
- AttributeName: variant_key
AttributeType: S
KeySchema:
- AttributeName: variant_key
KeyType: HASH
VariantDataBucket:
Type: AWS::S3::Bucket
Properties:
PublicAccessBlockConfiguration:
BlockPublicAcls: true
BlockPublicPolicy: true
IgnorePublicAcls: true
RestrictPublicBuckets: true
BucketEncryption:
ServerSideEncryptionConfiguration:
- ServerSideEncryptionByDefault:
SSEAlgorithm: AES256
QueryFunction:
Type: AWS::Serverless::Function
Properties:
CodeUri: src/
Handler: app.handler
Environment:
Variables:
VARIANT_TABLE: !Ref VariantTable
Policies:
- DynamoDBReadPolicy:
TableName: !Ref VariantTable
Events:
QueryVariant:
Type: HttpApi
Properties:
ApiId: !Ref BeaconApi
Path: /variants
Method: GET
Outputs:
ApiUrl:
Value: !Sub 'https://${BeaconApi}.execute-api.${AWS::Region}.${AWS::URLSuffix}/variants'
VariantTableName:
Value: !Ref VariantTable
VariantBucketName:
Value: !Ref VariantDataBucket
src/app.py:
import json
import os
import boto3
TABLE = boto3.resource("dynamodb").Table(os.environ["VARIANT_TABLE"])
REQUIRED = ("referenceName", "start", "referenceBases", "alternateBases")
def response(status_code, body):
return {
"statusCode": status_code,
"headers": {"content-type": "application/json"},
"body": json.dumps(body),
}
def handler(event, context):
params = event.get("queryStringParameters") or {}
missing = [name for name in REQUIRED if params.get(name) in (None, "")]
if missing:
return response(400, {"error": "missing query parameters", "fields": missing})
try:
start = int(params["start"])
if start < 0:
raise ValueError
except ValueError:
return response(400, {"error": "start must be a non-negative integer"})
variant_key = ":".join(
[
params["referenceName"],
str(start),
params["referenceBases"].upper(),
params["alternateBases"].upper(),
]
)
item = TABLE.get_item(
Key={"variant_key": variant_key},
ProjectionExpression="variant_key",
ConsistentRead=False,
).get("Item")
return response(
200,
{
"exists": item is not None,
"query": {
"referenceName": params["referenceName"],
"start": start,
"referenceBases": params["referenceBases"].upper(),
"alternateBases": params["alternateBases"].upper(),
},
},
)
部署并取得输出:
cd beacon-demo
sam build
sam deploy --guided
aws cloudformation describe-stacks \
--stack-name beacon-demo \
--query 'Stacks[0].Outputs' \
--output table
部署完成后,可以先向 DynamoDB 写入测试变异。将表名替换为 CloudFormation 输出值:
TABLE_NAME='替换为表名'
aws dynamodb put-item \
--table-name "$TABLE_NAME" \
--item '{"variant_key":{"S":"chr1:100:A:G"}}'
然后将 URL 替换为 ApiUrl 输出值:
API_URL='替换为ApiUrl'
curl -G "$API_URL" \
--data-urlencode 'referenceName=chr1' \
--data-urlencode 'start=100' \
--data-urlencode 'referenceBases=A' \
--data-urlencode 'alternateBases=G'
预期会得到类似响应:
{"exists": true, "query": {"referenceName": "chr1", "start": 100, "referenceBases": "A", "alternateBases": "G"}}
生产系统还需要处理参考基因组版本、染色体命名差异、变异左对齐与规范化、多等位基因拆分等问题。否则,同一个生物学变异可能生成不同的键,导致错误的阴性结果。
Athena 适合承接哪些任务
DynamoDB 适合已知键的快速读取,但不适合随意增加过滤条件。对于队列统计、数据质量检查或研究型探索,可以将规范化结果以 Parquet 格式写入 S3,再通过 Athena 查询。
下面的 SQL 只是一个可改造的示例,字段和 S3 路径需要按照实际数据布局调整:
CREATE EXTERNAL TABLE IF NOT EXISTS genomics.variants (
position BIGINT,
reference_bases STRING,
alternate_bases STRING,
sample_count BIGINT
)
PARTITIONED BY (
assembly STRING,
reference_name STRING
)
STORED AS PARQUET
LOCATION 's3://YOUR-BUCKET/variants/';
SELECT reference_name, COUNT(*) AS variant_count
FROM genomics.variants
WHERE assembly = 'GRCh38'
AND reference_name = 'chr1'
AND position BETWEEN 1000000 AND 2000000
GROUP BY reference_name;
为了控制 Athena 成本,应优先采用列式格式、压缩数据,并按照常用过滤字段设计分区。还可以配置 Athena workgroup 的扫描量限制,防止错误查询扫描整个数据湖。
在线 API 不应在每次精确查询时无条件启动 Athena。Athena 的优势是灵活扫描和分析,而不是替代低延迟索引。更稳妥的做法是让批处理流程生成 DynamoDB 查询索引,同时保留 S3 中的完整分析数据。
基因组查询的安全边界
“是否存在某个变异”看似只返回布尔值,但连续、有针对性的查询仍可能泄露敏感信息。无服务器架构会减少基础设施运维工作,却不会自动解决数据治理问题。
进入临床或受监管环境前,至少要检查:
- API 是否要求身份认证,并按数据集、项目或机构执行授权。
- 是否设置查询速率限制、异常模式检测和审计日志。
- Lambda 角色是否只拥有必要的 DynamoDB、S3 和 Athena 权限。
- S3、DynamoDB、日志以及查询结果是否使用符合要求的加密密钥。
- Beacon 响应是否需要阈值抑制、结果模糊化或审批机制。
- Athena 查询结果桶是否阻止公共访问,并设置生命周期策略。
- 日志中是否意外记录样本标识、完整查询参数或其他敏感数据。
对于高敏感度数据,还应考虑私有网络连接、组织级服务控制策略、跨账户隔离以及针对数据驻留要求的区域选择。
采用这类架构时如何取舍
sBeacon 展示了一条清晰路线:用 GA4GH Beacon 提供互操作接口,用 Lambda 承载无状态计算,用 DynamoDB 响应可预测的在线访问,用 S3 和 Athena保留大规模分析能力。
落地时可以按以下顺序推进:
- 先定义变异规范化规则和实际查询模式,再设计键和分区。
- 将最常见的精确查询放入 DynamoDB,不要把所有分析维度都做成在线索引。
- 使用 Parquet、压缩和合理分区降低 Athena 扫描成本。
- 在性能测试之外增加授权绕过、查询枚举和日志泄露测试。
- 通过调用次数、Lambda 延迟、DynamoDB 消耗和 Athena 扫描字节建立成本看板。
无服务器并不意味着容量设计消失,而是容量问题转化为索引设计、并发限制、数据布局和单次查询成本。对于基因组数据,这种拆分尤其重要:把快速回答与深度分析分开,才能同时守住延迟、费用和安全边界。