Supabase Edge Functions持久化存储和97%更快的冷启动

Supabase Edge Functions持久化存储和97%更快的冷启动
引言
今天,我们为Edge Functions引入了持久化存储功能,并将冷启动时间提升了高达97%。之前,Edge Functions只支持通过写入/tmp目录进行临时文件存储。许多执行任务的常用库,如文件压缩/解压缩和图像转换,都是基于持久化文件存储构建的,因此让它们在Edge Functions中工作需要额外的步骤。
⚡️ 更多Launch Week信息
持久化存储功能
持久化存储选项基于S3协议构建。它允许你将任何S3兼容的存储桶(包括Supabase Storage Buckets)挂载为Edge Functions的目录。你可以像在POSIX文件系统中一样对挂载的存储桶执行读取和写入文件等操作。
  1. // 从S3存储桶读取
  2. const data = await Deno.readFile('/s3/my-bucket/results.csv')

  3. // 创建目录
  4. await Deno.mkdir('/s3/my-bucket/sub-dir')

  5. // 写入S3存储桶
  6. await Deno.writeTextFile('/s3/my-bucket/demo.txt', 'hello world')
javascript
如何配置
要从Edge Functions访问S3存储桶,你必须在Edge Function Secrets中设置以下环境变量:
S3FS_ENDPOINT_URL
S3FS_REGION
S3FS_ACCESS_KEY_ID
S3FS_SECRET_ACCESS_KEY
如果你使用Supabase Storage,请按照此指南启用并创建访问密钥和ID。
使用案例:Edge Functions中的SQLite
S3文件系统简化了涉及读取和转换存储在S3存储桶中数据的工作流程。
例如,假设你正在构建一个IoT应用程序,其中设备将其SQLite数据库备份到S3。你可以设置一个计划的Edge Function来读取这些数据,然后将数据推送到你的主Postgres数据库进行聚合和报告。
  1. // 以下示例为便于阅读而简化

  2. import { DB } from "https://deno.land/x/sqlite@v3.9.1/mod.ts";
  3. import { supabase } from '../shared/client.ts'

  4. const today = new Date().toISOString().split('T')[0]
  5. const backupDBPath = `backups/backup-${today}.db`

  6. // 使用S3 FS读取SQLite数据库
  7. const data = Deno.readFileSync(`/s3/${backupDBPath}`);

  8. // 从S3下载的数据创建内存中的SQLite
  9. // 这比直接从S3读取更快
  10. const db = new DB();
  11. db.deserialize(data);

  12. function calculateStats(rows: IoTData[], date: string): StatsSummary {
  13. // ....
  14. }

  15. Deno.serve(async (req)=>{
  16. // 假设IoT数据存储在名为'sensor_data'的表中
  17. const rows = db.queryEntries(`
  18. SELECT * FROM sensor_data
  19. WHERE date(timestamp) = date('now', 'localtime')
  20. `)

  21. // 计算统计信息
  22. const stats = calculateStats(rows, today)

  23. // 将统计信息插入Supabase
  24. const { data, error } = await supabase
  25. .from('iot_daily_stats')
  26. .insert([stats])

  27. return new Response("OK");
  28. });
javascript
97%更快的函数启动时间,即使在负载下
之前,具有大型依赖项或在启动时进行准备工作的Edge Functions(例如,解析/加载配置、初始化AI模型)会产生明显的启动延迟。有时,这些慢邻居会影响同一台机器上运行的其他函数。Supabase Edge Functions Runtime中的所有JavaScript workers都在同一个Tokio线程池上进行协作调度。如果一个worker有繁重的启动逻辑,例如解析JavaScript模块或运行同步操作,它可能会延迟之后调度的每个worker。这导致高流量项目中偶尔出现长尾延迟峰值。
为了解决这个问题,我们将仍在执行初始脚本评估的workers移到了专用的阻塞池中。这种方法防止繁重的初始化任务阻塞Tokio线程,显著减少了其他函数的启动时间峰值。
结果
启动时间现在更加可预测,冷启动的等待时间现在快得多。以下是我们进行的一项基准测试的结果,比较了这些更改前后的启动时间。
指标
之前
之后
(变化)
平均
870 ms
42 ms
95%
P95
8,502 ms
86 ms
99%
P99
15,069 ms
460 ms
97%
最差
24,300 ms
1,630 ms
93%
峰值 > 1s
47%
4%
43 pp
同步API支持
通过将函数启动时的昂贵计算卸载到单独的池中,我们能够在函数启动期间启用同步文件API的使用。一些库只支持同步文件API(例如SQLite),这将允许你在Edge Functions开始处理请求之前设置它们。
你现在可以在初始脚本评估期间安全地使用以下同步Deno API(及其Node对应项):
Deno.statSync
Deno.removeSync
Deno.writeFileSync
Deno.writeTextFileSync
Deno.readFileSync
Deno.readTextFileSync
Deno.mkdirSync
Deno.makeTempDirSync
Deno.readDirSync
请记住,同步API仅在初始脚本评估期间可用,在HTTP处理程序或setTimeout等回调中不支持。
  1. Deno.statSync('...') // ✅

  2. setTimeout(() => {
  3. Deno.statSync('...') // 💣 错误!Deno.statSync在当前上下文中被阻止
  4. })

  5. Deno.serve(() => {
  6. Deno.statSync('...') // 💣 错误!Deno.statSync在当前上下文中被阻止
  7. })
javascript
今天在Preview上试用
这些更改将与Deno 2升级一起在未来2周内推广到所有集群。同时,如果你想今天试用它们,可以使用Preview集群。请参阅此指南了解如何在Preview集群中测试你的函数。
Launch Week 15
主舞台
第1天 - 介绍JWT签名密钥
第2天 - 介绍支持Iceberg的Supabase Analytics存储桶
第3天 - 介绍Branching 2.0
第4天 - 介绍Supabase中的新可观测性功能
第5天 - 介绍Edge Functions的持久化存储
构建舞台
01 - Supabase UI: Platform Kit
02 - 使用Figma Make创建Supabase后端
03 - 介绍stripe-sync-engine npm包
04 - 改进的安全控制和安全的新家园
05 - Supabase的Algolia连接器
全球社区聚会
技术细节
持久化存储的优势
文件系统兼容性:支持标准文件操作
S3协议支持:与现有S3生态系统完全兼容
性能优化:减少网络延迟和传输开销
成本效益:利用现有的S3存储基础设施
冷启动优化原理
专用阻塞池:将初始化任务隔离到专用线程池
异步调度:避免阻塞主事件循环
资源隔离:防止单个函数影响其他函数
智能缓存:优化模块加载和依赖解析
实际应用场景
1. 文件处理管道
  1. // 图像处理示例
  2. import { Image } from "https://deno.land/x/imagescript@1.2.17/mod.ts";

  3. Deno.serve(async (req) => {
  4. // 从S3读取原始图像
  5. const originalImage = await Deno.readFile('/s3/uploads/original.jpg');
  6. // 处理图像
  7. const image = await Image.decode(originalImage);
  8. const resized = await image.resize(800, 600);
  9. const processed = await resized.encode();
  10. // 保存处理后的图像
  11. await Deno.writeFile('/s3/processed/resized.jpg', processed);
  12. return new Response('Image processed successfully');
  13. });
javascript
2. 数据分析和报告
  1. // 数据分析示例
  2. import { parse } from "https://deno.land/std@0.208.0/csv/mod.ts";

  3. Deno.serve(async (req) => {
  4. // 读取CSV数据
  5. const csvData = await Deno.readTextFile('/s3/data/sales.csv');
  6. const records = parse(csvData, { skipFirstRow: true });
  7. // 分析数据
  8. const totalSales = records.reduce((sum, record) =>
  9. sum + parseFloat(record.amount), 0);
  10. // 生成报告
  11. const report = {
  12. totalSales,
  13. recordCount: records.length,
  14. averageSale: totalSales / records.length
  15. };
  16. // 保存报告
  17. await Deno.writeTextFile('/s3/reports/daily-sales.json',
  18. JSON.stringify(report, null, 2));
  19. return new Response(JSON.stringify(report));
  20. });
javascript
3. 机器学习模型服务
  1. // ML模型服务示例
  2. import { load } from "https://deno.land/x/onnx_runtime/mod.ts";

  3. let model = null;

  4. // 在启动时加载模型
  5. try {
  6. const modelData = await Deno.readFile('/s3/models/sentiment-analysis.onnx');
  7. model = await load(modelData);
  8. } catch (error) {
  9. console.error('Failed to load model:', error);
  10. }

  11. Deno.serve(async (req) => {
  12. if (!model) {
  13. return new Response('Model not loaded', { status: 500 });
  14. }
  15. const { text } = await req.json();
  16. // 进行预测
  17. const prediction = await model.run({ input: text });
  18. return new Response(JSON.stringify({ prediction }));
  19. });
javascript
最佳实践
1. 错误处理
  1. Deno.serve(async (req) => {
  2. try {
  3. const data = await Deno.readFile('/s3/my-bucket/file.txt');
  4. return new Response(data);
  5. } catch (error) {
  6. console.error('File read error:', error);
  7. return new Response('File not found', { status: 404 });
  8. }
  9. });
javascript
2. 性能优化
  1. // 缓存文件内容
  2. let cachedData = null;

  3. Deno.serve(async (req) => {
  4. if (!cachedData) {
  5. cachedData = await Deno.readFile('/s3/my-bucket/large-file.json');
  6. }
  7. return new Response(cachedData);
  8. });
javascript
3. 批量操作
  1. Deno.serve(async (req) => {
  2. const files = ['file1.txt', 'file2.txt', 'file3.txt'];
  3. const results = [];
  4. for (const file of files) {
  5. try {
  6. const content = await Deno.readTextFile(`/s3/my-bucket/${file}`);
  7. results.push({ file, content });
  8. } catch (error) {
  9. results.push({ file, error: error.message });
  10. }
  11. }
  12. return new Response(JSON.stringify(results));
  13. });
javascript
迁移指南
从临时存储迁移
  1. // 旧方式 - 临时存储
  2. const tempFile = '/tmp/data.json';
  3. await Deno.writeTextFile(tempFile, JSON.stringify(data));

  4. // 新方式 - 持久化存储
  5. const persistentFile = '/s3/my-bucket/data.json';
  6. await Deno.writeTextFile(persistentFile, JSON.stringify(data));
javascript
环境变量配置
  1. # 在Supabase Dashboard中设置
  2. S3FS_ENDPOINT_URL=https://your-s3-endpoint.com
  3. S3FS_REGION=us-east-1
  4. S3FS_ACCESS_KEY_ID=your-access-key
  5. S3FS_SECRET_ACCESS_KEY=your-secret-key
bash
总结
Supabase Edge Functions的持久化存储和冷启动优化为开发者提供了:
持久化文件存储:支持S3兼容的存储桶
显著的性能提升:冷启动时间减少97%
同步API支持:在启动期间支持同步文件操作
更好的可扩展性:资源隔离和智能调度
这些改进使得Edge Functions更适合处理文件密集型任务,如数据处理、图像处理和机器学习模型服务。