写点什么

我在产品上线前不小心删除了 7 TB 的视频

  • 2022-05-16
  • 本文字数:4113 字

    阅读完需:约 13 分钟

我在产品上线前不小心删除了7 TB的视频

今天我们想分享的是一位初级开发者对于自身犯的某个错误的记录。这个帖子在日前在 Hacker News 之所以引起很多人的讨论和共鸣,或许是因为许多经验丰富的工程师都是这么走过来的。


虽然这是一个“新手贴”,但它也是不错棒的帖子。犯错不可怕,最重要的是,犯错后能吸取经验教训,让自己会变得更强大。这位开发者把它写下来巩固自己学习过程中的理解,这对任何有类似经验的初级开发人员来说都是有积极意义的。人人都会犯错,关键是下一步怎么做。以下是帖子内容,我们将其翻译出来,希望能为读者带来参考价值:

前言

先跟大家打个招呼,我是个刚工作还不到一年的初级开发者,所以很多在各位看来不言而喻的道理我自己还没摸索清楚。本文权当记录成长过程中的点滴,请大家轻拍。


在很多高手们看来,这个故事简直不可理喻……没错,里头有太多坏习惯、太多低级错误,在硅谷巨头看来完全不可想象。但这就是世界各地小型 IT 企业的真实生存状态,而且还在继续困扰着无数像我这样的从业者。


我目前是在意大利一家小开发公司(一共 10 个人)上班,主要是为当地企业开发管理各种网站和工具。另外,我们还跟意大利、英国和南非最大的几家健身企业签订了大额合同。既然说是最大的健身企业,那他们应该知道自己想要什么喽?错,他们完全不知道……当然,我不打算把锅甩给他们,毕竟这里的甲方和乙方都是一屁股烂账、谁也别说谁。总之,让咱们客观回顾事件原委。

从项目说起

受保密协议所限,这里我没法透露太多。简而言之,我们目前开发的项目需要使用大量视频,这些视频素材托管在 Vimeo 之上。公司当前用的是 VimeoOTT,也就是负责内容交付的前端平台,但上头打算逐步迁移至 Vimeo Enterprise。VimeoOTT 上需要迁移的视频大概有 500 段,但 Vimeo 并不提供简单易行的迁移方法。去年 10 月左右,我曾经写信给对方的支持团队,询问他们能不能帮助迁移,回复中说他们“会调查一下”。然后就没有然后了。


所以说,我们得重新上传这些视频素材。我提议构建一个自定义 API 脚本,从 OTT 那边下载视频、再把素材上传至 Enterprise(和我们的产品)。但管理层拒绝了这套方案,而是决定花钱请人来手动完成。接下来的一个月中,有人上传了来自 OTT 的 500 段视频外加 400 段新视频,于是我们一下子就用光了 Enterprise 提供的全部 11 TB 存储容量中的 9 TB,好在一切进展顺利(虽然效率不高)。时间很快来到今年 4 月。

出问题了

突然之间,Vimeo 那边似乎开了窍,想起我们之前提出的申请。于是在并未告知我司的情况下,他们决定把 OTT 上的所有视频都转储到 Enterprise 新平台上。但为什么不打个招呼呢?可能 Vimeo 根本就不关心吧。


  1. 他们重复上传了我们这边已经传过的视频。

  2. 现在视频素材的总大小在 15 TB 左右,超出上限 4 TB。就是说除非我们删除一部分内容,否则根本没法继续上传视频。我们询问 Vimeo 能否恢复更改,但得到的却是否定的答复。最要命的是,再有一个礼拜左右产品就该上线了。


唯一的选择就只能是手动删除多出来的视频了,这活归我来干。很遗憾,我犯了个巨大的错误。

“解决方案”

(介绍一下背景,之前 7 个月里我一直在使用 React,这也成了引爆问题的直接导火索)


幸运的是,我们在数据库里为每段视频都分配了一个“VimeoId”,所以我脑袋里蹦出的第一个解决方案就是:


for each video in vimeo:    if video not in our_vimeo_ids:    delete("api.vimeo.com/videos/{video}"
复制代码


这两条请求都是分页的(只是具体方式略有不同),所以我编写的实际代码是:


page = 0url = f"https://api.ourservice.com/media?page={page}&step=100"our_ids = []for i in range(10):    page = i    res = requests.get(url)    videos = res.json()['list']    ids = [video['vimeoId'] for video in videos]    our_ids += ids
next = '/me?page=1'vimeo_ids = []while next is not None: res = requests.get(f'https://api.vimeo.com/videos{next}') res = res.json() videos = res['data'] ids = [video['id'] for video in videos] vimeo_ids += ids next = res['pagination']['next']
for id in vimeo_ids: if id not in our_ids: requests.delete(f'https://api.vimeo.com/videos/{id}')
复制代码


大家肯定一看就知道错在哪了,现在我也看得出来。但当时我检查了好几遍,觉得它没有任何问题。这里剧透一下答案:


url = f"https://api.ourservice.com/media?page{page}&step=100our_ids = []for i in range(10):    page = i    res = requests.get(url)
复制代码


我当时已经习惯了 React,所以天然认定 url 会在页面变量更改后立即刷新,但事实并非如此。所以在使用这个脚本之后,所有不存在于我们数据库第一页里的视频都会被从 Vimeo 中删除。


这里还有另一个问题:我测试了代码,并使用了以上示例中的这个错误循环。


page = 0url = f"https://api.ourservice.com/media?page{page}&step=100our_ids = []for i in range(10):    page = i    res = requests.get(url)    videos = res.json()['list']    ids = [video['vimeoId'] for video in videos]    for id in ids:    res = requests.get(f'https://api.vimeo.com/videos/{id}')        if res.status_code != 200:        print(f"There was something wrong. You have deleted a wrong video -> {id}")
复制代码


我还做了几次手动测试,但测试范围就只有数据库上的第一页。哎,这本该很容易避免的一系列错误。

善后工作

好消息是,Google Drive 文件夹里还有一份视频备份,而且相关信息也好好保存在数据库内。坏消息是,这时候时间已经来到星期五,而我们最晚也得在星期二早上就把视频还原回去。当时公司的网络带宽大概是每秒 30 MB,上传数据总量在 8 TB 左右。不现实……我们得想点别的办法。


我想到的第一个解决方案就是用 Google Drive API。我们这边有全部上传到数据库的视频文件名,所以我很快写下以下代码:


page = 0file_names = get_our_filenames(page) # This time without the mistake in the for loopfor name in file_names:    download_and_save_from_drive(name)    upload_to_vimeo(name)
复制代码


这意味着我可以用不同页面多次运行脚本,从而在不同网络上“并行化”整个过程。(我也想过换个高速网络环境,但一是没有比较快捷的提速途径、二是出站流量成本过高。)效果还是不理想,毕竟就算是饱和传输也得占满整整 4 天,再出一丁点问题就要超时。于是我又想到了一个办法:


另一个解决方案


能不能直接把视频从 Google Drive 上传到 Vimeo?我检查了一下上传页面,并发现确实可以这么操作。只是还有个小问题:它只支持手动操作,无法使用 API 自动优化,但优势是上传几乎可以即时完成。也许还有更好的办法,但我当时真的想不到了,所以我满心欢喜地启动了 Playwright。


Playwright 是一款自动 E2E 工具,可用于模拟用户交互。具体来讲,它可以按我们编程的指引点击网站上的不同位置。有它在,不就把人给解放出来了?


下面来看代码:(我刚开始用 Playwright、而且时间紧迫,所以写得确实粗糙,请大家原谅)


test('Videos', async ({ page }) => {  // 登录进vimeo  await page.goto('https://vimeo.com/upload/videos');  await page.fill(    'input[name="email"]',    'xxx'  );  await page.fill('input[name="password"]', 'xxx');  await page.click('input[type=submit]');
// 点击Drive按钮,然后登录进 Google Drive // 我们需要将其作为iframe来管理 const [popup] = await Promise.all([ page.waitForEvent('popup'), page.click('text=Drive'), ]); await popup.fill('input[type="email"]', 'xxx'); await popup.click('button:has-text("Next")'); await popup.fill('input[type="password"]', 'xxx'); await popup.click('button:has-text("Next")'); await timeout(5000);
// 对于我们上传前就已经获得的全部文件名 for (let i = 0; i < videos.length; i++) { if (i > 0) { page.click('text=Drive'); await timeout(5000); } let found = false; while(!found) { for (let frame of page.frames()) { const searchbox = await frame.$('input[aria-label="Search terms"]'); const button = await frame.$('div[data-tooltip="Search"]'); if (searchbox) { await temp.fill(videos[i]); await button.click(); } } } await timeout(5000);
// 每当搜索时,Google都会重新生成iframe,所以我们就得重新搜索 for (let frame of page.frames()) { const temp = await frame.$('table[role="listbox"] div[tabindex="0"]'); if (temp) { const select = await frame.$('div[id="picker:ap:2"]'); await select.click(); } } await page.goto('https://vimeo.com/upload/videos'); }});
复制代码


这段代码确实不怎么样(特别是其中用超时来解决 Playwright 中.click()的部分),但它还是发挥了符合预期的效果,只有一个意外:我没能让它正确点击查找到的视频,而只是点到了“Select”按钮上。直到现在,我也不知道这个问题该怎么解决。所以就算是用上这段代码,我也得每 10 秒就手动单击一次来选择视频,这样才能让程序持续运行。我坐在屏幕前点了 10 分钟,然后开始怀疑自己这是在搞什么鬼……


我下载了一个自动点击器(xclicker),并把它设置成每 5 秒点击一次。成功!每段视频大概耗时 13 秒,一共 1000 段视频,用了 4 个小时所有视频就都上传完成了。现在只剩最后一步:


它们都有了新的“vimeoIds”,所以我得回到公司这边的数据库,用正确的 Id 值更新所有视频。但这次要简单得多,用跟之前类似的 Python 脚本就能轻松完成。


最后,所有视频上传完成,产品终于能顺利上线了。

总结

这事让我学到了什么?首先就是在执行破坏性操作之前,先充分进行测试。也希望 Vimeo 和外包商也能从中吸取教训吧,虽然我怀疑他们根本不在乎。(肯定不在乎,直到现在这种上传方式还是只支持手动,想想这是为什么!)


原文链接:


https://blog.thevinter.com/posts/vimeo

2022-05-16 18:465717
用户头像
罗燕珊 AI practitioner | Tech media

发布了 523 篇内容, 共 381.9 次阅读, 收获喜欢 842 次。

关注

评论 1 条评论

发布
用户头像
Playwright是否类似于国内的“按键精灵”呢?
2022-05-19 13:28
回复
没有更多了
发现更多内容

硬核!阿里大佬都在内卷的SpringBoot从入门到实战笔记

Java你猿哥

Java Spring Boot ssm 实战 Spring全家桶

强大的ai技术图像编辑器:Luminar Neo 激活版

真大的脸盆

图像编辑 编辑图像 图像处理工具

如何重装mac系统,u盘安装苹果macos系统教程

互联网搬砖工作者

携手共进丨九科信息入选信通院“铸基计划”高质量数字化转型产品及服务全景图,并受邀出席高质量数字转型创新大会

九科Ninetech

这六种目前最常见分布式事务解决方案!请拿走不谢

三十而立

Java 程序员 分布式 IT

面试造飞机?GitHub顶级“java面试手册2023”(统计通过率95%)

三十而立

Java GitHub 面试 java面试

TGO笔记-AIGC分享之投资视角(61/100)

hackstoic

AI ChatGPT 创业投资

一个小网站的云原生实践

松然聊技术

架构 云原生

如何学习分布式系统,分布式是什么,这里有很好的解释,很全

三十而立

Java 分布式

爆火!阿里新版23年面试突击进阶手册,Github标星51k!

Java你猿哥

Java 面试 ssm 面经 八股文

Photoshop 2023 (版本 24.2)的新增功能和增强功能

互联网搬砖工作者

PyTorch 深度学习实战 | 基于YOLO V3的安全帽佩戴检测

TiAmo

数据采集 PyTorch

手把手教你Mac重装系统不再难:苹果电脑重装系统教程

互联网搬砖工作者

牧云助手:一款面向技术爱好者的远程主机管理工具

百川云开发者

运维 主机管理 终端远程协助

云智一体,深入生命科学

Baidu AICLOUD

基因测序 AI制药 AI for Science

华大北斗芯片亮相纽伦堡国际嵌入式展EW2023

江湖老铁

自己动手写虚拟机

ScratchLab

虚拟机 kvm

焱融科技荣登《2022中国企业数智化创新TOP50》榜单

焱融科技

文件存储 分布式文件存储 数智化 高性能存储 全闪存储

浅析三款大规模分布式文件系统架构设计

Java你猿哥

架构 分布式 架构设计 分布式架构 系统架构设计手册

互联网工程师1000道Java面试题整理全集,助你一路绿灯

Java你猿哥

Java 面试 SSM框架 八股文 Java八股文

SpringBoot 集成 Druid 数据源

Java你猿哥

Java Spring Boot 后端 ssm Druid

Java实战干货|Spring Boot整合MyBatis框架快速实现数据操作

三十而立

Java spring springboot

Jetpack-Compose 学习笔记(二)—— Compose 布局你学会了么?

修之竹

android 前端 android jetpack

马士兵教育2023年全新Java架构师学习路线「首发版」

Java你猿哥

Java 学习 架构 面试 后端

抽丝剥茧还原真相,记一次神奇的崩溃

阿里技术

debug

DevOps|研发效能不是老板工程,是开发者服务

laofo

DevOps cicd 研发效能 持续交付 平台工程

Notification(状态栏通知)详解

芯动大师

android Android Studio Notification

SpringBoot 实现 MySQL 百万级数据量导出并避免 OOM 的解决方案

Java你猿哥

Java MySQL spring Spring Boot ssm

三天吃透Spring Cloud面试八股文

程序员大彬

Java 面试 SpringCloud

战损版JavaAgent方法耗时统计工具实现

Java你猿哥

Java Spring Boot Java Agent ssm

我在产品上线前不小心删除了7 TB的视频_文化 & 方法_thevinter_InfoQ精选文章