2017新年好

2016年,我做了这些事:

– 写了8篇博客

博客空间总访问量61198 PageView(Google Analytics数据),比前一年稍有下降,但是实际的访问量应该还没有这么多,因为发现Google Analytics结果中出现了相当数量的Spam数据,暂时还没研究怎么能去过滤掉。跟去年一样,首页、在Linux下使用“360随身WiFi 2”calibre常见问题为Raspberry Pi 2编译内核模块这几个页面的PV占去了总PV的50%多。饭否发消息85条,包括照片17张。

– 自由软件相关

接手了一个网站:Linux Kernel Patch Statistics。这个网站的内容是按人、公司、国家等维度的指标去统计各Linux内核版本中Patch的数量。我很偶然地看到有人在LKML中吐槽说这个网站的域名过期了。这个网站的作者是我以前的同事,于是我联系他提醒他,没想到他表示说不打算继续维护这个网站了。我觉得就这么放弃一个在社区有一定影响力的网站有点可惜,所以在征得他同意的前提下,把这个网站接了过来,并且还请朋友帮忙把过期的域名给抢注了回来。

网站恢复运行的不到一个月时间中,我已经收到各种数据订正、添加功能和Bug报告的邮件,看来这个网站的的价值比我想象的还大一些。不过这个网站的后台代码确实是经久失修,所以目前数据统计的精准度非常糟糕(因为根据邮件地址把数据按公司、国别来归类,这里面的映射关系绝大部分是需要人肉来维护的,一旦没有及时维护,归类为Unknown的数据就会越来越多,也就失去了统计的意义),而且每天一次全量数据产出过程需要占用大量的CPU、IO和内存资源。所以后面需要优先先维护一下基础数据,保证统计数据质量,然后再考虑下整体的重构问题。

calibre贡献了一点点代码,改写了一下从Amazon获取书籍元信息的插件,使之可以支持中国亚马逊网站。给HBaseFlink的代码/文档各修正了一处Typo,其实只是为了实践一下给这两个项目Contribute的流程,不过后来由于工作内容的变化,没有再深入关注过这两个项目。给C++异步框架Seastar修正了一处Bug。train-graph合并了一些其它人贡献的代码和数据,发布了一个3.0版本。

– 几个IT产品

群晖DS916+ NAS:淘汰了原来用的DS214play,主要是出于盘位和性能的考虑。不过新的机器的性能依然很让人捉鸡。不过出于对DSM系统版权的尊重,我还是没有选择自己买机器组黑群晖的方案。我的群晖系统的评价依然是:轻度使用很不错,重度使用时细节缺失很多、问题也很多。但是市面上已经找不出更好的了。

Macbook Air:公司提供的工作电脑,我的第二台苹果设备(N年前得到过一个iPod Nano)。这样的电脑用来做开发机实在是性能捉鸡,尤其是为了编译Linux程序再启一个Docker的情况下。公认的优点就不说了,缺点就是有些Windows能做的事情它还是不能做,而有些Linux能做的事情它也不能做。对于我这种已经被Linux虐了十年的人来说,不能做Windows能做的事是很容易接受的,但现在不能做的事的又变多了,所以还有点不爽,于是有了下面的Dell 7040m。

Dell 7040m微型台式机:为了更有效的开发Linux C++程序,买了这个微型台式机当工作机。配置成i7 6700T的CPU,16G内存,SM951的SSD,装Arch Linux。实际用下来整体能满意,但是就编译大型C++程序来说,单核性能仍然不是非常出色。另有同事买了相似配置的Intel的Skull Canoyo,也是差不多的体验。我也知道我的应用场景下应该买个标准台式机才能配置更好性能的CPU,但是谁让我这个机器的外型的毒呢?

华硕AC68U路由器:其实去年买的AC66U完全够用了,不过还是因为一次特价剁手了更高端一点的AC68U。整体使用体验与AC66U相仿。不过从外观来说,我反而还更喜欢AC66U一点,AC66U给人的感觉是做工精致、用料实足。AC68U其实也一样,但给我的感觉却是:傻大笨粗。

Raspberry Pi 3:没啥说的,我是树莓派的脑残粉,出一个买一个。相对2来说,主要就是64位、内置蓝牙和Wi-Fi,性能稍有提高,别的就没有了。树莓派是吃灰神器是名不虚传的,这个现在已经吃灰。还买了一个Sense HAT传感器模块,做了一个贪吃蛇游戏后也吃灰了。有了3以后,我用以前吃灰的2和Camera Module做了一个网络摄像头,配合群晖做监控,效果勉强凑合。

Pebble 2:本来是在Kickstarter预定了Pebble Time 2,但因为正在用的Pebble花屏越来越严重,等不及就先收了一个Pebble 2,没想到次日Pebble就宣布被fitbit收购了(我觉得与其说是收购,不如就当是破产了更合适),Time 2也没有机会再问世了。Pebble 2的使用体验与Pebble高度一致,我很满意,只可惜这已经是绝唱了。希望在它坏掉以前,能有更出色的产品出现。

二手Kindle Paperwhite 2:跟以前用的Paperwhite比,差别并不大,只不过因为Paperwhite被老妈重度使用中,所以自己重买一个。没买3是因为性价比,毕竟我也不是重度使用。而且看书这个单一需求来说,我并不觉得Paperwhite 1/2/3/Kindle Voyage有多大的差别。

二手Intel Compute Stick:我需要一台常开的Windows机器来满足把NAS上的照片上传到 Google Photos的需求,这个东西很符合我的要求,功耗不到5W,直接由路由器USB口供电就可以了。性能对于我来说也完全够用,有了它不但解决了Google Photos上传的问题,甚至我的电脑上已经不再需要安装Windows虚拟机了,偶而需要用Windows的时候,直接远程桌面连上去用就可以了。

– 出行

南京*3、合肥、西安。对南京的感情依然不变、合肥真不是一个旅游城市、第二次去陕西省历史博物馆已找不回第一次去时惊艳的感觉。

展望2017年:

谈点工作,在用Java写了4年业务代码后,2016年,我终于又回归了技术开发。在短暂地用了一段时间Scala后,还回归到了C++开发。说是“回归”,其实还是更大的挑战,因为需要用C++ 14来写一些高性能的分布式程序,对于我来说也仍然是一个全新的课题。期待2017年可以在这个方面能有所收获。

关注2017维也纳新年音乐会

曾经关注过的那些维也纳新年音乐会:关注维也纳新年音乐会,2017年将是我第22次收看维也纳新年音乐会的直播。

2017维也纳新年音乐会CD封面

2017维也纳新年音乐会CD封面

  • 01 Franz Lehár – Nechledil Marsch aus der Operette Wiener Frauen – 内西雷迪尔进行曲(选自喜歌剧《维也纳的妇女》)
  • 02 Èmile Waldteufel – Les Patineurs; Walzer; op. 183 – 溜冰者圆舞曲
  • 03 Johann Strauss II – ‘s gibt nur Kaiserstadt, ‘s gibt nur Wien!; Polka; op. 291 – 只有帝国之都,只有维也纳(皇城)波尔卡 – 1997
  • 04 Josef Strauss – Winterlust; Polka schnell; op. 121 – 冬趣快速波尔卡 – 2005
  • 05 Johann Strauss II – Mephistos Hollenrufe; Walzer; op. 101 – 梅菲斯特的地狱圆舞曲 – 1995
  • 06 Johann Strauss II – So angstlich sind wir nicht!, op. 413 – 我们决不畏惧波尔卡 – 2009
  • 07 Franz von Suppé – Ouvertüre zu Pique Dame – 黑桃皇后序曲
  • 08 Carl Michael Ziehrer – Hereinspaziert!; Walzer; op. 518 – 闲庭信步圆舞曲 – 1979
  • 09 Otto Nicolai – Die lustigen Weiber von Windsor, Moon Choir – 月升小合唱(选自轻歌剧《愉快的温沙妇人》)
  • 10 Johann Strauss II – Pepita-Polka; op. 138 – 细花纹方格波尔卡
  • 11 Johann Strauss II – Rotunde-Quadrille; op. 360 – 圆形大厅四对舞
  • 12 Johann Strauss II – Die Extravaganten; Walzer; op. 205 – 奢靡圆舞曲
  • 13 Johann Strauss I – Indianer-Galopp; op. 111 – 印度人加洛普 – 2004
  • 14 Josef Strauss – Die Nasswalderin; Polka mazur; op. 267 – 纳斯瓦尔德的女孩玛祖卡波尔卡 – 1996
  • 15 Johann Strauss II – Auf zum Tanze!; Polka schnell; op. 436 – 跳舞吧快速波尔卡
  • 16 Johann Strauss II – Tausend und eine Nacht; Walzer; op. 346 – 一千零一夜圆舞曲 – 1992, 2005
  • 17 Johann Strauss II – Tik-Tak; Polka schnell; op. 365 – 提塔快速波尔卡 – 1979, 2002, 2012
  • 18 Eduard Strauss ? – ?
  • 19 Johann Strauss II – An der schönen blauen Donau, Walzer, op. 314 – 蓝色多瑙河圆舞
  • 20 Johann Strauss I – Radetzky-Marsch, op. 228 – 拉德茨基进行曲

2017年维也纳新年音乐会即将迎来首次登上该音乐盛事指挥台的指挥家古斯塔沃·杜达梅尔。做为当代最为杰出的指挥家之一,80后的杜达梅尔也将成为有史以来新年音乐会上最年轻的指挥。

年轻的指挥家为这传统古老的音乐会带来了新的气息,这次音乐会正式演出的17个曲目中,有8首是第一次在新年音乐会上与乐迷见面,剩下的9首中也有7首是只在新年乐会上演出过一次的“冷门”曲目。加演的第一个曲目目前还没有公布,如果“路边社”消息属实,加演的是爱德华的一首快速波尔卡,那么这次音乐会的曲目单中就将史无前例地出现九位作曲家的名字,新年音乐会在演绎施特劳斯家族音乐的传统之上,越来越多的融入了更多其他作曲家的作品。

维也纳音乐之友协会合唱团将第一次加入新年音乐会的演出行列,在下半场与爱乐乐团一同演绎“月升小合唱”。

中央电视台从1987年开始转播维也纳新年音乐会,到现在已经有30年。我从1996年开始收看新年音乐会,到现在已经超过了20年。经历了刚开始的陌生和新奇以及中间的狂热,新年音乐会如今已经成为了一种习惯。我用上面的文字列举完了音乐会曲目单的各项“技术参数”以后,惊讶地发现,对于每一个具体的曲目,我竟然写不出任何文字再去深入的点评它们。那些曾经演出过的曲目,在脑海中的印象似乎也变得越来越模糊。不过我也不打算像以前那样把曲目单上的曲子都找出来复习+预习一遍了,我相信在新年到来的时候,乐团的演绎会让我回忆起那些曾经熟悉旋律,那种与老朋友相见的感觉想必是非常美好的。

期待新年音乐会,也期待新的一年。

十年(中)

这文章已经快烂尾了,努努力,先补个中段出来。

TL;DR

其实我已经不太想写后面的内容了,写这篇文章时我已经仔细地想过我所希望在文章中表达出来的内容。总结到最后,我觉得这十年中让我自己受益最大的一点就是:突破自我,走出舒适区,积极、主动地多走一步。

对于我自己来说,即使到现在,我也常常很难主动去打破自己的舒适区,积极地多走一步。但是回首过去的十年,但凡我多走了一步,无论是主动还被迫,最终都得到了超越预期的结果。

这里说的多走一步,还不仅仅局限于自己的事。在跟别人合作的时候或者打交道时,多走一步,或者帮助别人多走一步,都是非常有益的。

2009年

产品维护的工作比想象中的更难做,尤其是换到了一个全新的产品线后,对于一个完全不了解的产品,需要同时去给客户解决多个平台上N个版本产品中出现的各种疑难杂症,实在是一件非常有挑战的事情。

有两个Case印象深刻:

  1. 有个Case我完全无从下手,只好开始“忽悠”,说了自己的一些怀疑点,让核心技术支持部门协助客户去收集更多的日志,趁收集日志的时间我就可以在后台更进一步地分析问题。有一天突然就发现,美国开发团队以前开发这个产品的一位Principal Engineer(首席工程师?反正级别很高,是个传奇人物)跳进了这个Case。他不但详细的分析了问题的原因,给出解决思路和方案,还“顺手”把代码改好、打好补丁包。后来我向他致谢的时候,他说:这个产品就像是我的孩子,我有责任把它照顾好。
  2. 有位客户发现产品界面上的CPU占用率曲线在产品连续运行了两个月左右后就会显示成持续占用100%,但实际CPU占用率并没有这么高。我从前台Java Script一直到后台程序及内核代码全部分析了一遍以后终于发现,是因为程序中用32位无符号整形数来表示开机后到当前时间的毫秒数,所以每过49.7天,这个整形数就会溢出,造成问题。我修掉了这个问题,并且定制了一个Linux内核,可以在启动时指定uptime的初始值用于快速重现这个Bug,把Patch和这个测试用内核一起提交给QA。QA后来表示非常感激,因为如果没有这个测试用内核,要Mock出一个很大的uptime值是一件很麻烦的事情。

这两个Case说的都是Ownership,说的都是怎么在尽职的前提下再多走一步。在一个人人都很优秀的环境下,“尽职”只能算是一个基本要求,想要有额外的收获,你必须有主动的额外的付出。

有了前两年“脱宅”的积累,2009年全年的每个休息日我基本上都在南京城里逛来逛去,与南京这座城市的感情也越发深厚。尤其是在经历了一些事情后,让我对人的情感有了更为深入的理解,这对我后面的生活产生了很多积极的影响。很多事情,即使不能多走一步,也应该多想一点。多想一点,也许就能让一个原本优秀的人变得更为卓越。

除了没事逛来逛去,我也把很多很多时间放在了南京图书馆。这一年看了很多闲书,包括铁路、艺术、建筑、地理、文学、数学、摄影、设计等领域。现在回想起来,真是怀念这段时间,可以肆无忌惮地把大块时间花在“不务正业”的事情上。看了这么多闲书,并不可能帮助我在这些领域中有所建树,但是对于我来说,人生的乐趣就在于不断地学习、不断地探索未知。有时候,时间就是用来浪费的。

2010年

我的第三位Manager是一个爱憎分明的人,我有幸成为他比较喜欢的下属之一,所以他给了我充分自主发挥的空间,也为我争取了很多很多的机会,甚至为了帮我争取一个去美国参加会议的机会,“强迫”另一位Manager退掉了去美国的机票,把参会的名额留给我。当然,更重要的是他在工作中给我创造的机会和对我的各种想法的重视和支持。那时公司也很鼓励微创新,我的各种微创新在他的帮助下都得以在团队内或产品中落地生根,这给了我极大的自信,也帮助我在部门内找到了属于我的位置。

机遇和挑战总是并存的,在这个开发团队,我负责了这个产品两个大版本中最重要的两个大Feature,从前期方案调研到最后开发实现,统统要搞定。还是得益于公司完善的流程和管理, 这两个版本软件做完以后,我完整了实践了两遍软件从需求到发布的完整流程,从中学到了很多的东西。同时,这两个Feature也涉及到很多跟美国开发团队以及国外一些第三方公司的打交道的事,所以又进一步提高了我跟老外们打交道的能力。这一段经历,不但让我在技术上有所收获,更重要的是让我学到了做事的方法,很多事情并不会自动朝着你想象的方向去发展,怎么才能推动事情往前迈步,既需要努力,也需要很多的技巧。其中最基本的技巧就是要合理计划、分解任务制定小的里程碑、踏实实践

这一年的业余时间花了很多在自由软件上,更多的参与到一些简单自由软件项目中去。再次提起Ownership一词,在自由软件世界里,你可以既是软件用户,也是软件的主人。当你以软件主人的心态去使用自由软件时,才会真正理解自由软件背后的理念,也才能从中收获更多的价值。

 

十年(上)

上周末是参加工作十周年,回南京与同一年进入公司的老同事们聚了聚,很开心,也有颇多感想。

对于我在Trend Micro的第一份工作,一直以来,我是心存感激的。从学校走向社会,从稚嫩变得慢慢成熟,回头去看,有很多人、很多事都值得感激。

总结一下过去的十年,顺便熬一锅心灵鸡汤。

2006年

离开学校,走进公司。趋势科技应聘经历里提到的那位在一面时给了我很多自信的面试官成了我的第一位Manager。放到现在,也许我不会说事无巨细、无微不至的Manager是一位足够Professional的Manager,但是作为职场新人,这样的Manager给了我足够的帮助。他对我的信任、给我提供的机会和帮助,都为我的成长提供了一个良好的环境。后来,我做过新人的Mentor、也带过小项目,践行了很多从他身上学到的东西。

公司提供的培训,帮助我理解了Development Process、Design Documents、Technical Writing、Internationalize & Localization、User Centered Design、Writing Secure Code相关的知识,并在短时间内都在工作中得到了实践,这些经验一直受用至今。虽然互联网公司并不适合照搬传统软件公司的开发流程,但是很多方法思路依然值得借鉴。

技术方面,虽然1999年我就装过玩过Linux,但一直到工作前对Linux都还处于基本不懂的状态。工作的前半年,我学习了Linux下的各种开发工具,从无到有独立完成了产品中一个GTK程序的开发,学会了修改了内核模块的代码和内核调试的基本工具。后面五年中用到的各种知识和技能中的一大半都是在这半年中积累起来的。学东西最快的途径就是看书加实践,学而不思则罔,学而不实践则很难真正把书上的东西变成自己的。

ServerProtect for Linux 3.0

我参与开发的第一个产品ServerProtect for Linux 3.0(图片来自网络)

公司让大家考了托业(TOEIC),并开始上英语课,还推行了一段时间英文会议。于是原先只出现在邮件交流中的各种“洋泾浜”英语,开始出现在日常的会议中,挂在了每个人的嘴边。这些并不完美的英语,却真正的帮助到了我,从这以后,基本上我就摆脱了“哑巴英语”的困境 。

英语课上,老师推荐了一部电影《Dead Poet Society》,在这部电影里,我看到了自己的影子,这部电影让我改变了很多。以Carpe diem的理由,我开始努力摆脱曾经自己给自己定下的条条框框,去尝试更多的“不可能”。

2007年

年初,我的Mentor也许是因为偷懒,让我帮他去上原本属于他的Effective Presentation培训。没有想到,短短一天培训中学到的一些基础的演讲技能、对“紧张”的正确认识以及对演讲前充分准备的重要性的认识,就帮助我从一个不敢在公开场合发言的人,变成了一个不太惧怕公开演讲的人。培训导师通过理论和实践帮助我建立了自信。

为了开源产品中的内核模块,我深入理解了GNU工程及自由软件哲学,研究了各种自由软件/开源许可证,并开始积极参与到自由软件社区中。

为了实践Customer Insight,生平第一次出差、第一次坐飞机。我一直以为那时自己做的产品其实没有太多实用价值,当看到自己的产品在客户的环境中真正发挥着作用时,自豪感优然而生。

因为“Carpe diem”,这一年,我的行为举止发生了很多变化。发展了不少的小众爱好:暴走火车地铁无线电,并因此结交了几位挚友。自己去参加CSDN的技术大会,开始关注Web 2.0。通过开发“豆饭”,开始与互联网开发结下不解之缘。通过参与UCDChina的活动,与本地互联网公司的设计师们建立了良好的关系。我的社交圈子在这一年中,扩大了很多,并且很多时候都是我自己主动出击,这在以前,我是绝对做不到的。

2008年

由于参与开发的产品已经在前一年完成并发布,工作角色从产品开发转为产品维护。这段日子是我的工作经历中最为灰暗的日子。首先,产品维护需要在有限的时间内解决客户的问题,我在学会了“沟通技巧”和“做事方法”的同时,放弃了对技术精益求精的追求。为了降低Resolution Time,我学会了忽悠,而不是去寻找Root Cause。其次,新的Manager强调学习做事方法高于技术追求,他让我在各种挫折中自己去学会在职场中生存下来的技能,但是各种挫折不断地打击着我刚刚积累起来的一点点自信心。

然而,这种种不如意,却只是成长的代价。我这一年的技术积累没有前两年那么多,但依靠那些被逼迫着学到的职场技能,加上各种机缘巧合,反而倒奠定了我在团队中的地位。然而“捷径”只是加速达到某些目标的一种手段,真正起作用的,依然是踏踏实实的每一步。

公司开始强调文化建设,难听的说法叫“洗脑”。也许是那个时候还比较Simple和Naive,这次文化建设的培训让我受益非浅。直到现在,我依然认同那时被灌输的思想:你需要让你个人变得更加优秀,才能发挥出更大的价值。

参加了公司的Home Building Project,这是公司与一个公益组织Gawad Kalinga合作进行的公益项目,去菲律宾盖房子(这个翻译其实不对,这是Home Building,不是House Building,盖房子只是一种行为,目的是帮助那里的人们拥有更美好的家)。第一次走出国门、第一次与来自不同国家和地区的同事合作、第一次与这么多小朋友近距离接触、第一次与生活在贫穷与自然灾害面前的人们面对面交流。过去了这么多年,当初的震撼已经慢慢的淡去,但留在内心深处的东西,也许会影响一个人的一生。

Home Building Project的合影

Home Building Project的合影

那时公司组织各种活动很多,加上那段时间我自己也参与了不少线下的活动,从中自己积累和总结了很多组织线下活动的技能。3月底自告奋勇为当时的Unix-Center网站成立一周年组织了南京地区的线下活动,活动虽然规模不大,但很成功,甚至还吸引了上海、无锡等周边城市的自由软件爱好者。这次活动也为后来成立Nanjing Linux User Group埋下了种子。

Unix-Center周年庆

Unix-Center周年庆

Synology PhotoStation性能优化

本文的内容已经过时,6.7.0以上的Photo Station已经没有严重的性能问题了。

我折腾过不少家用NAS方案,包括最早的WD My Book World Edition,后来的Joggler,再到后来的Raspberry Pi,这些方案除了性能存在一些问题以外,最大的缺点就是易用性存在问题,不但非“专业”人士用起来存在困难,就连我自己也对土法泡制的照片管理功能感到不满。直到两年前入了群晖(Synology)的家用NAS DS214play,事情才变得安逸起来。

平心而论,Synology的系统虽然功能强大,体验也还不错,但细节上做得其实真是挺糙的。最近就发现了它的照片管理软件PhotoStation出现了严重的性能问题,在照片库里只存了8万余张照片的情况下,打开首页要花费的时间已经超过了20秒,每打开一个文件夹都需要等待10秒以上,几乎不可用了。

以下分析以DSM 6.0为例,PhotoStation版本为6.4-3166。PhotoStation的安装路径为/volume1/@appstore/PhotoStation。

Synology的DSM系统,后台使用PostgreSQL数据库,前端是PHP页面。简单推理一下就可以知道,PhotoStation的性能瓶颈主要是在对照片索引数据库的访问上。性能调优的第一步就是先要找到哪些SQL查询占用了太多的时间。打开PostgreSQL记录SQL查询的开关,并查看所有SQL执行情况:

$ sudo su postgres
$ vi ~/postgresql.conf
log_statement = 'all'
$ psql photo postgres
# SELECT pg_reload_conf();
# \q
$ exit
$ sudo tail -f /var/log/postgresql.log

通过查看SQL执行记录,很容易发现几个明显的慢查询:

1.

SELECT COUNT(*) as total FROM photo_image; 
SELECT COUNT(*) as total FROM video;

2.

SELECT COUNT(logid), MAX(logid) FROM photo_log;

3.

SELECT * FROM (
    SELECT path AS filename, timetaken AS takendate, 
        create_time AS createdate, 'photo' AS type
    FROM photo_image
    WHERE path LIKE '/volume1/photo/%'
        AND path NOT LIKE '/volume1/photo/%/%' AND disabled = 'f'
    UNION
    SELECT path AS filename, mdate AS takendate,
        date AS createdate, 'video' AS type
    FROM video
    WHERE path LIKE '/volume1/photo/%'
        AND path NOT LIKE '/volume1/photo/%/%'
        AND disabled = 'f'
    ) AS totalCount; 

4.

SELECT path, resolutionx, resolutiony, version FROM (
    SELECT path, resolutionx, resolutiony, version,
        create_time, privilege_shareid, disabled
    FROM photo_image WHERE privilege_shareid IN (
        SELECT shareid FROM photo_share WHERE ref_shareid = (
            SELECT shareid FROM photo_share WHERE sharename = '2016'))
    AND disabled = 'f'
    UNION ALL
    SELECT path, resolutionx, resolutiony, 0 AS version, 
        date AS create_time, privilege_shareid, disabled
    FROM video WHERE privilege_shareid IN (
        SELECT shareid FROM photo_share WHERE ref_shareid = (
            SELECT shareid FROM photo_share WHERE sharename = '2016'))
    AND disabled = 'f') temp
ORDER BY create_time DESC LIMIT 1; 

优化的思路很简单,由于PhotoStation在正常情况下访问数据库所需要的读性能是远远大于写性能的,所以就通过牺牲写性能来逐一击破上面这些慢查询:

1. 程序的目的就是想知道系统中有多少张照片和多少个视频(而且其实并不需要精确知道,差不多就行),可惜对于PostgreSQL来说,由于它采用MVCC来解决并发问题,SELECT COUNT(*)是一个需要进行全表扫描的慢操作。解决方案就是用另外用一张表来存这两个表的总记录条数,并在原表上添加触发器来更新记录数。

CREATE TABLE photo_count (table_oid Oid PRIMARY KEY, count int);
ALTER TABLE photo_count OWNER TO "PhotoStation";
CREATE FUNCTION count_increment() RETURNS TRIGGER AS $_$
BEGIN
  UPDATE photo_count SET count = count + 1 WHERE table_oid = TG_RELID;
  RETURN NEW;
END $_$ LANGUAGE 'plpgsql';
CREATE FUNCTION count_decrement() RETURNS TRIGGER AS $_$
BEGIN
  UPDATE photo_count SET count = count - 1  WHERE table_oid = TG_RELID;
  RETURN NEW;
END $_$ LANGUAGE 'plpgsql';
CREATE TRIGGER photo_image_increment_trig AFTER INSERT ON photo_image 
  FOR EACH ROW EXECUTE PROCEDURE count_increment();
CREATE TRIGGER photo_image_decrement_trig AFTER DELETE ON photo_image 
  FOR EACH ROW EXECUTE PROCEDURE count_decrement();
CREATE TRIGGER video_increment_trig AFTER INSERT ON video 
  FOR EACH ROW EXECUTE PROCEDURE count_increment();
CREATE TRIGGER video_decrement_trig AFTER DELETE ON video 
  FOR EACH ROW EXECUTE PROCEDURE count_decrement();
INSERT INTO photo_count VALUES 
  ('photo_image'::regclass, (SELECT COUNT(*) FROM photo_count));
INSERT INTO photo_count VALUES 
  ('video'::regclass, (SELECT COUNT(*) FROM video));

然后在PHP程序中修改需要统计表数记录数的逻辑,在这里可以看到似乎同一个Session中只会查一次,但即使就是这一次,也已经慢得让人不开心了:

diff --git a/photo/include/file.php b/photo/include/file.php
index 541c5cb..7caa5de 100755
--- a/photo/include/file.php
+++ b/photo/include/file.php
@@ -536,8 +536,11 @@ class File {
 		if ($key && isset($_SESSION[SYNOPHOTO_ADMIN_USER][$key])) {
 			return $_SESSION[SYNOPHOTO_ADMIN_USER][$key];
 		}
-		$query = "SELECT count(*) as total FROM $table";
-
+		if ('photo_image' == $table || 'video' == $table) {
+			$query = "SELECT count as total FROM photo_count where table_oid='$table'::regclass";
+		} else {
+		    $query = "SELECT count(*) as total FROM $table";
+		}
 		$result = PHOTO_DB_Query($query);
 		if (!$result) {
 			// db query fail, won't update session value

2. 其实可以用跟前一个问题类似的方法去解决。但是这个其实只是一个没太多用处的操作日志表,所以我用更为简单粗暴的方法去解决这个问题:减少在数据库中保留日志的条数。直接修改PHP程序:

diff --git a/photo/include/log.php b/photo/include/log.php
index 1c982af..56385db 100644
--- a/photo/include/log.php
+++ b/photo/include/log.php
@@ -1,8 +1,8 @@
 <?php
 
 class PhotoLog {
-	const LIMIT = 100000;
-	const PURGECOUNT = 10000;
+	const LIMIT = 1000;
+	const PURGECOUNT = 100;
 	public static $SupportFormat = array("html", "csv");
 
 	public static function Debug($msg)

3. 第三个问题主要体现在对照片路径的处理上,为了选出位于某个路径下(不含子目录)的照片,程序采用了path LIKE ‘/path/%’ AND path NOT LIKE ‘/path/%/%’这样的查询条件。其实PostgreSQL在一定程度上是可以利用path字段上的索引来很好的优化这个查询的,但是实际运行中发现(通过在PostgreSQL的客户端中用explain和explain analyze分析查询)在某些情况下索引会失效,造成非常差的查询性能。解决方案还是用写性能来换读性能,先在表上加一个dirname字段并建立索引,按path算好文件所在的目录名写入dirname,然后把查询条件改为对dirname的查询,避免使用通配符和LIKE运算即可:

ALTER TABLE photo_image ADD COLUMN dirname TEXT NOT NULL DEFAULT '';
UPDATE photo_image 
    SET dirname = LEFT(path,LENGTH(path)-STRPOS(REVERSE(path),'/')+1);
ALTER TABLE video ADD COLUMN dirname TEXT NOT NULL DEFAULT '';
UPDATE video SET dirname = LEFT(path,LENGTH(path)-STRPOS(REVERSE(path),'/')+1);
CREATE INDEX dirname_index ON photo_image USING btree(dirname);
CREATE INDEX dirname_index ON video USING btree(dirname);
CREATE OR REPLACE FUNCTION set_dirname()
 RETURNS trigger
 LANGUAGE plpgsql
AS $function$
BEGIN
  NEW.dirname := LEFT(NEW.path,LENGTH(NEW.path)-STRPOS(REVERSE(NEW.path),'/')+1);
  RETURN NEW;
END $function$
CREATE TRIGGER set_dirname_trigger 
BEFORE INSERT OR UPDATE ON photo_image 
FOR EACH ROW 
  EXECUTE PROCEDURE set_dirname();
CREATE TRIGGER set_dirname_trigger 
BEFORE INSERT OR UPDATE ON video 
FOR EACH ROW 
  EXECUTE PROCEDURE set_dirname();

同时修改PHP程序:

diff --git a/photo/include/photo/synophoto_csPhotoDB.php b/photo/include/photo/synophoto_csPhotoDB.php
index ac8f932..43e58ee 100755
--- a/photo/include/photo/synophoto_csPhotoDB.php
+++ b/photo/include/photo/synophoto_csPhotoDB.php
@@ -1607,10 +1607,8 @@ class csSYNOPhotoDB {
 		} else {
 			$albumRealPath = self::EscapeLikeParam(SYNOPHOTO_SERVICE_REAL_DIR_PATH."{$albumName}/");
 		}
-		$cond = "path LIKE ? {$this->escapeStr} AND path NOT LIKE ? {$this->escapeStr} AND disabled = 'f' ";
-		array_push($pathSqlParam, "{$albumRealPath}%");
-		array_push($pathSqlParam, "{$albumRealPath}%/%");
-
+		$cond = "dirname = ? {$this->escapeStr} AND disabled = 'f' ";
+		array_push($pathSqlParam, "{$albumRealPath}");
 		if (!$removePhoto) {
 			$photoQuery = "SELECT path as filename, timetaken as takendate, create_time as createdate, 'photo' as type
 FROM photo_image WHERE $cond";
 			$sqlParam = array_merge($sqlParam, $pathSqlParam);

4. 这个查询只是为了查询一个目录及其所有子目录中最新一个照片或视频,用其缩略图来作为目录的封面图片。群晖的工程师自己也知道这个查询很慢,所以还在程序中加了个逻辑,当照片视频数量大于200000时,放弃按日期排序,直接随机选一张。然而,这个查询实际上是可以简单优化的,明明不需要把所有的照片视频UNION到一起后再找出最新的一个,可以直接分别找出最新的照片和最新的视频,然后再到这两个中去取一个相对更新的就可以了。直接修改PHP代码实现:

diff --git a/photo/include/photo/synophoto_csPhotoAlbum.php b/photo/include/photo/synophoto_csPhotoAlbum.php
index ca128f0..f0e57e7 100755
--- a/photo/include/photo/synophoto_csPhotoAlbum.php
+++ b/photo/include/photo/synophoto_csPhotoAlbum.php
@@ -145,9 +145,11 @@ class csSYNOPhotoAlbum {
 		$cond .= " AND disabled = 'f'";
 
 		$table = "(" .
-				"SELECT path, resolutionx, resolutiony, version, create_time, privilege_shareid, disabled FROM pho
to_image WHERE $cond " .
+				"(SELECT path, resolutionx, resolutiony, version, create_time, privilege_shareid, disabled FRO
M photo_image WHERE $cond " .
+				"ORDER BY create_time DESC LIMIT 1)" .
 				"UNION ALL " .
-				"SELECT path, resolutionx, resolutiony, 0 as version, date as create_time, privilege_shareid, disa
bled FROM video WHERE $cond " .
+				"(SELECT path, resolutionx, resolutiony, 0 as version, date as create_time, privilege_shareid,
 disabled FROM video WHERE $cond " .
+				"ORDER BY create_time DESC LIMIT 1)" .
 		") temp";
 
 		// this may cost lots of time, so it won't sort by create_time if the total count exceeds the threshold (200,000)

做完以上优化,我的PhotoStation已经基本可以做到点进文件夹秒开了,至少我自己已经比较满意了。声明一下,其实我并不太懂数据库相关理论和技术,以上“优化”只能说是在我自己的实验中起到了优化的效果,也许其中一些并不太科学,希望这篇文章能起到抛砖引玉的作用。

重新安装PhotoStation或升级DSM系统会造成我们对程序所作的修改丢失,所以在修改完成后,务必做好备份。

另外,适时对PostgreSQL数据库进行VACUUM操作似乎可以起到提高访问性能的目的,尤其是在做过大量照片更新后。