写点什么

我在产品上线前不小心删除了 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:465690
用户头像
罗燕珊 AI practitioner | Tech media

发布了 514 篇内容, 共 367.6 次阅读, 收获喜欢 830 次。

关注

评论 1 条评论

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

姐姐驾到 | 零基础小白如何学前端!

锋享前端

万亿储能的极限拉力赛

钛禾产业观察

kubernetes下的Nginx加Tomcat三部曲之三

程序员欣宸

Java Kubernetes 5月月更

网站开发进阶(五十四)jQuery获取父级元素、子级元素、兄弟元素方法汇总

No Silver Bullet

JQuery框架 5月月更

C语言_标准时间与秒单位的转换

DS小龙哥

5月月更

趣学设计模式-代理模式

ZuccRoger

5月月更

共同推动基础软件根技术发展,华为与中国软件行业协会签署战略合作协议

科技热闻

【ELT.ZIP】OpenHarmony啃论文俱乐部——大数据框架性能优化系统

ELT.ZIP

大数据 OpenHarmony 压缩算法 ELT.ZIP

数据湖揭秘—Delta Lake

阿里云大数据AI技术

sql spark 分布式计算 关系型数据库 存储

YUV数据分析

Loken

音视频 5月月更

极狐GitLab入驻阿里云计算巢,共同提升云上开发体验

阿里云弹性计算

DevOps 计算巢

SAP 订单模型的编排方式概述

汪子熙

订单管理 订单 5月月更 b2b 编排系统

【架构学习09】——电商秒杀系统

tiger

架构实战营

GaussDB(for Influx)与开源企业版性能对比

华为云开发者联盟

数据库 开源 查询 写入 GaussDB(for Influx)

赵海鹏:如何进行OpenHarmony音频特性架构设计和开发工作

OpenHarmony开发者

OpenHarmony 开发者故事 开发者说

为了让女朋友运动起来,小伙儿不仅买单车还设计了智能防盗单车锁

华为云开发者联盟

stm32 华为云IoT 智能防盗单车锁 蓝牙

存储卷指标消失之谜 | K8S Internals 系列第二期

BoCloud博云

Kubernetes kubelet

来自2022年的Python 网络爬虫补充知识,HTML+JSON+爬虫场景

梦想橡皮擦

5月月更

位运算小妙招-求二进制序列中1的个数

芒果酱

c++ C语言 5月月更

druid源码学习一

Nick

源码 Druid

得物技术消息中间件应用的常见问题与方案

得物技术

kafka 分布式 MQ 中间件 消息队列

互联网用户画像,精准营销,数仓有妙招

华为云开发者联盟

位图 GaussDB(DWS) 用户画像 精准营销 Roaringbitmap

『Python』题集⒋

謓泽

Python 5月月更

【LeetCode】乘积小于 K 的子数组Java题解

Albert

LeetCode 5月月更

面试突击47:死锁产生的原因有哪些?

王磊

Java 面试 java面试

5 月 20 日,API 网关 Apache APISIX Summit ASIA 2022 重磅来袭

API7.ai 技术团队

开源 API网关 Apache APISIX APISIX 网关 APISIX Summit

沙利文发布《2021年中国数据库市场报告》:中国分布式数据库2021专利占全球76%

科技热闻

【架构学习10】——毕业总结

tiger

架构实战营

ptrace注入分析

小道安全

区间合并算法

工程师日月

算法 5月月更

明道云入选爱分析2022年两份低代码研究报告

明道云

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