写点什么

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

发布了 546 篇内容, 共 418.4 次阅读, 收获喜欢 860 次。

关注

评论 1 条评论

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

软件测试/测试开发丨持续交付-Jenkinsfile 语法

测试人

软件测试 自动化测试 测试开发

交易履约之产品中心实践

京东科技开发者

交易 京东云 京东技术 京东科技 产品中心

聊一聊系统重构

中台的悖论

agnostic

中台

什么是容器编排及编排的优点

黎博

容器编排 Kubernetes Serverless

作为移动开发你不能不了解的编译流程

京东科技开发者

编译器 移动开发 京东云 京东技术

美团:某动态线程池框架是官方开源的么?

马丁玩编程

线程池 美团线程池

AAA级认证!索信达综合信用水平获高度认可

索信达控股

CMS系统是什么?

源字节1号

开源 软件开发 前端开发 后端开发 小程序开发

AR市场为何频频“呼唤”苹果?

Alter

AR

常用对话框基本使用

芯动大师

dialog timepicker progress

手把手带你上手ChatGPT

老周聊架构

3月月更 ChatGPT

线段树模板与练习

timerring

线段树

架构实战营 - 模块五作业(微博评论)

🐢先生

架构实战营

利用 ChangeStream 实现 Amazon DocumentDB 表级别容灾复制

亚马逊云科技 (Amazon Web Services)

一文吃透扫码登录原理

程序员大彬

Java java面试 扫码

如何实现云数据治理中的数据安全?

京东科技开发者

云计算 大数据 数据治理 企业号 3 月 PK 榜 计算资源

PyTorch深度学习实战 | 神经网络的优化难题

TiAmo

PyTorch 随机梯度下降 动态调整

软件测试/测试开发丨MockServer 服务框架设计

测试人

软件测试 自动化测试 测试开发

运维训练营第19周作业

好吃不贵

Tars-Java网络编程源码分析

vivo互联网技术

网络编程 nio TARS

实现常驻任务除了避免昙花线程,还需要避免重返线程池

newbe36524

C#

基于 Kafka 和 Elasticsearch 构建实时站内搜索功能的实践

京东科技开发者

MySQL ES 京东云 京东物流 京东技术

Three.js 进阶之旅:物理效果-3D乒乓球小游戏 🏓

dragonir

CSS JavaScript 前端 React three.js

gt-checksum 1.2.1发布,新增表结构校验及修复等超实用特性

GreatSQL

MySQL greatsql社区 gt-check

软件测试/测试开发丨持续交付-Pipeline入门

测试人

软件测试 自动化测试 测试开发

系统设计的端到端原则

俞凡

架构

人工智能与软件工程

紫晖

人工智能 机器学习 软件工程 工程

在京东如何做好前端系统的可观测性

京东科技开发者

前端 京东云 京东技术

如何实现云数据治理中的数据安全?

京东科技开发者

数据库 云计算 京东云 京东技术

简历上的项目,需要这样描述才有亮点!

小傅哥

Java 面试 项目 简历 校招

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