On Reading CS Papers – Thoughts & Reflections

Be forewarned:

  • This is not an advice post. There are tons of people out there who desperately want to give people advice on reading papers. Read theirs, please.
  • This post is a continuous reflection on the topic “how to read a CS paper” from my personal practice. I will list out my academic status before each point so that it may be interesting to myself on how my view on the matter has changed as time goes forward.

2018

The first year of my CS master program. Just get started on CS research.

  • It’s OK to not like a paper

In my first semester, I majorly read papers on Human Computation and Crowdsourcing.  Very occasionally, I read papers on NLP. Some papers on NLP are from extra readings in Greg’s course. Some are related to Greg’s final project, which deals with both code and language.  I don’t really like and want to read papers back then. In NLP class, I prefer to read textbooks (Jufrasky’s one) and tutorial posts that I can find online. One roadblock for me to read papers is that there is certain background knowledge gap I need to fill and I just simply don’t know how to read a paper. So, for Greg’s NLP course, I only read some papers related to my final project. This paper is the base paper for my final project. I got this paper from professors in linguistics and software engineering and they want me to try out the same idea but using neural network model instead. I read this paper several times and the more I read, the more I want to throw up.  I just think this paper hides many critical implementation details and the score 95% is just too high for me to believe. The authors open source their code but their code has some nasty maven dependencies, which won’t compile under my environment. Their evaluation metric is non-standard in NLP and many “junk words” wrap around their results. Of course, the result of my experiment is quite negative.  I often think it is just a waste of life to spend your precious time on some paper you dislike.  Here, I’m more of talking about paper writing style and the reproducibility of papers’ results. I probably want to count shunning from some background gap as a legitime reason not like a paper.

  • Try to get most of the paper and go from there

I got this message from Matt’s Crowdsourcing class. In the class, I have read a very mathematical heavy paper, which invokes some combinations of PGM and variational inference on the credibility of fake news. I’m worried back then about how should I approach a paper like this one, which I’m extremely lack of background and mathematics formula looks daunting.  I pose my doubts on Canvas and Matt responds in class and gives the message.  I think the message really gives me some courage on continuing read papers.

  • It’s OK to skip (most) parts of a paper.  Remember: paper is not a textbook!

This semester I’m taking a distributed system class. To be honest, distributed system paper can be extremely boring if they are from industry. Even worse, system paper can be quite long: usually around 15 pages, double column. So, if I read every word from beginning to end, I’ll be super tired and the goal is not feasible for a four-paper-per-week class. So, I have to skip. Some papers are quite useful maybe just for one or two paragraphs. Some papers are useful maybe just because of one figure. As long as your expectation about a paper gets met, you can stop wherever you want.

  • Multiple views of reading a paper

I didn’t get the point until very recently. I did quite terrible on the first midterm of my distributed system class. The exam is about how to design a system to meet a certain requirement. In the first half of the course, I focus on the knowledge part presented by the paper but that doesn’t work out well. Until then, I realize that I need to read those systems paper from a system design point of view: what problems they need to solve, what challenges they have, how they solve the challenges.  OF course, those papers are valuable from knowledge perspective: how consistent hashing works, for example. But, depends on the goal of reading paper, I can prioritize different angles of reading a paper. If I need to implement the system mentioned in the paper, I probably need to switch to a different paper reading style.

  • Get every bit of details of paper if you need to

It’s time again for the final course projects. Again, I need to generate some ideas and find some baseline papers. In this case, “skip parts” and “get most out of the paper and move on” strategy probably won’t work well. All in all, I need to understand the paper and those are rely on the details from the paper. In this case, I need to sit through the whole journey and remove any blockers that I may encounter.

Advertisements

Some thoughts on learning

刚才在YouTube首页随便打开了一个推荐视频,视频的名字叫做How to Learn Faster with the Feynman Technique (Example Included)。我个人其实很少点开这种带有非常强烈功利色彩的标题去看的,更别提是这种方法论的视频。并不是说我讨厌这类视频,我只是觉得这类视频看多了,如果不立刻去实践的话,看了也没有任何效果,反而浪费掉了很多时间。这个视频本身不长5分钟左右,看完后,我不仅觉得这个方法可以试一试,而且更重要的是让我回忆起过去看过的一些东西以及一些想法。所以赶紧记录在这里。

方法本身并不复杂。具体其实可以看作者这个网页文字版。我就粗略记录一下要点:

这个方法的核心就是explaining the concept。一个典型场景就是去office hour找老师问问题:在你向老师解释完问题,你的疑惑点,以及你的解决方法之后,很有可能出现的情况就是还没等老师说话,你就说:“噢,我懂了。谢谢老师!” 然后转身跑出了办公室。这个场景体现出两个要点:1. explaining the concept确实非常重要 2. 也许这个问题已经在我们的内心里过了无数遍,却只有我们向另一个人解释的时候我们才会 “噢!!!!”。 第一点不难理解。但是第二点确是极度困难:我们从哪里能找到一个愿意天天听我们explain what we have learned的人呢? 上面的视频提出了一种解决办法,步骤如下:

  1. 找张纸,把你要解释的概念写在纸的最上头
  2. 假装有一个第三方在场,用你自己的语言向他解释这个概念。这里作者有几点强调:
    1.  用最通俗易懂的语言来解释
    2. 不要仅限于定义。挑战自己,用例子,图等形式来确定你可以把所解释的概念实际运用起来。
    3. 把在场的这个第三方想像成一个小孩子。这么做的好处就是小孩子会经常问“为什么?” 这个会让我们做到对这个概念的细节有百分百的理解。
  3. 在做完前两步之后,你就要重新回顾你在哪些地方解释出现模糊,拿不准,或者不知道。这些地方就是我们对这个概念理解薄弱的地方。我们要做的就是拿出资料将这些模糊点搞清楚。这步和考完试改错题非常相似。
  4. 这步其实是第三步的引申。就是看之前哪些解释的地方运用了复杂的语言或者堆砌了大量技术概念。我们要做的就是尽力把这些地方用更简洁的语句进行重写。

视频介绍的这个方法其实在各种我所看过的资料里都有体现过。比如在The Pragmatic Programmer一书中讲如何debug一节时,作者介绍的一个技巧叫做”Rubber Ducking”:

A very simple but particularly useful technique for finding the cause of a problem is simply to explain it to someone else. The other person should look over your shoulder at the screen, and nod his or her head constantly (like a rubber duck bobbing up and down in a bathtub). They do not need to say a word; the simple act of explaining, step by step, what the code is supposed to do often causes the problem to leap off the screen and announce itself.

It sounds simple, but in explaining the problem to another person you must explicitly state things that you may take for granted when going through the code yourself. By having to verbalize some of these assumptions, you may suddenly gain new insight into the problem.

这个”Rubber ducking”的来源就是其中一位作者在早期的时候看见一个很厉害的developer经常拿着一个”a small yellow rubber duck, which he’d place on his terminal while coding”。

The Lady Tasting Tea一书的前言中作者也提到作者在写着本书的时候收到了来自他的妻子很多的帮助。因为他的妻子不是statistician,所以如果作者用很technical的语言来写的话,他的妻子根本就看不懂。所以这迫使作者不得不采用更加通俗易懂的语言来解释统计背后的哲学及观念发展。

这也让我想到我最开始写Tech blog的一个主要motivation就是把我所学的东西通过文章的形式解释出来,争取消灭掉不懂装懂的情况。而视频方法的3,4步就是未来我在写tech blog的时候需要更加注意的地方:不要把所学的内容进行简单的归乃重复,要注意用更加简略的语言讲述出来。这里题外话一句:我发现将知识点内嵌在文章里是一个非常不错的保存知识点的方式。这么做我感觉会使知识点不会过于碎片化,并且提供充足的上下文将独立的知识点很有效的串联起来。这也许是“connect the dots”的一种体现吧。

最后用一个quote来作为这篇post的收尾:

“The first principle is that you must not fool yourself — and you are the easiest person to fool.” — Richard Feynman

Under Construction (part 2/2)

In the previous post, I briefly recap the effort I have made to build a personal website. As you can tell from my previous work, I’m super into Sphinx-doc tool and I’m seeking to build a website that is more suitable for industry professional. So, this post is more about technology selection and what’s going on with my endeavor now.

“Everyone should have a blog”?

There is a tendency for person who works in tech industry: they love to blog about the technology. I’m not indicating that I’m against this tendency. In fact, I’m super into this idea. That makes programmer life much easier. When we run into technical questions, we can google and usually the solution is presented by some nice guy’s blog post. However, I’m not sure if making blog post is a great way to systematize your knowledge. Let’s take “Minimal Emacs Tutorial” as an example. Let’s imagine how we can translate this article into a blog post. “Learn about Emacs” section can be a post and then I tag it with “emacs-terms”, “emacs-commands”. “HowTos” section is separated into multiple posts because each howto is collected on different dates and as a blogger, I would make a blog post each time I found something new with emacs usage. The tags for “HowTos” section posts may be “emacs topics”. Now, if I want to take a look at what I have learned on Emacs, then I three operations: click on “emacs-terms” to find the posts with this tag, then “emacs-commands”, and finally “emacs topics” to traverse through the all emacs-related posts. As you can see, this is tiresome. Some may argue that this issue can be fixed if we add a theme tag, like “emacs” to every emacs-related post. This can workaround the problem but we need to be very careful about how we choose the tag.

That being said, however, I think making technical blog post’s advantage outweighs its disadvantage. Most important thing about writing blog is that you can keep track of your progress daily. I have been making my personal knowledge base for more than a year. I’m pretty satisfied with what I have accumulated so far. However, there are some caveats. Off top of my head,  I need more direct visualization of my progress. I want to see if I can pick up something new daily and that will give me a lot more motivation to push myself to learn more each day. In addition, some page can get enormously long. I have a page called “Collection of algorithms”, which takes like forever to scroll from top to bottom. This page is naturally suitable for blog post by making each algorithm into a post. Lastly, for marketing purpose. Making a technical blog post daily is an enormous effort and it will definitely look good for hiring managers – “Zeyuan is passionate about technology!”. Even though I can showcase my private knowledge base, that is much more inconvenient than a blog post that can be accessed by everyone.

You know you have a wordpress blog, right?

Yeah, I know. WordPress.com is a great place to write blogs. It makes me want to write something each month. However, I want to say writing technical posts on wordpress.com is sort of painful. In general, if you need to insert code, you need to work with “HTML” editor and use

[sourcecode language="csharp"]
//your code comes here
[/sourcecode]

This format is incompatible with reStructuredText. That means the content written here cannot be rendered using Sphinx-doc engine. That essentially makes the portability of the post to the minimum. In addition, I experience some weird bug when insert the source code. Inside “Sqoop2 7 Minutes Demo with DB2” post, I use lots of SQL statements inside the code block. Everything works fine the first time. However, when I try to update the post, all the quotation marks get translated to the html representation under “Visual” editing tab. I have to manually update all the quotation marks to its symbol form.

However, on the other hand, wordpress.com blog works best with photos. I really enjoy how wordpress.com manage photos in my “Trip in Nan Jing” and “First Time Ever Hackathon” posts.

What should use?

This is the question that I have been looking into for months. Let me list my expectation first:

  • Support reStructuredText
  • Static pages should be fine and must feel LIGHTWEIGHT
  • Support basic blog functionality – tags, categories, post date

Let’s list out what commonly-seen options are:

  • Octopress
  • hexo
  • Sphinx-doc
  • Pelican

Let’s cross Octopress first. I don’t want to touch Ruby ecosystem because the workflow to develop a personal site will completely be different. In addition, there are plugins that let Octopress support reStructuredText but I figure that doesn’t offer full capability like Sphinx-doc does.

Hexo is another beast. It is similar to Octopress in the sense that both of them use Markdown as their major language and it is also rooted in front-end world. I admit Node.js is a pretty cool language and the theme provided by Hexo looks amazng. However, it feels too modern to me and I want to take a little bit more conservative path.

I want to talk about Sphinx-doc and Pelican all together. Sphinx-doc is the foundation to write Python documentation and Pelican is based upon Sphinx-doc and tweak towards blog post. Tweaking Sphinx-doc towards a blog post site requires a fair amount of knowledge of Jinjia2 and some Javascript knowledge. I don’t have enough time for that. So that makes Pelican very tempting. However, I don’t want to use it for now as the workflow and how to control the Pelican behavior is quite different from Sphinx-doc. However, it is definitely worth revisiting in the future just because the size of community and plenty of themes.

The engine I choose is called Tinkerer. It’s the direct derivative of Sphinx-doc and the workflow doesn’t deviate from the standard Sphinx-doc workflow too much and the setup is quite the same. However, the latest release is back in 2014. That makes me a little worry. But let’s stick with this for now. I can easily switch to Pelican whenever I want to.

What does look like right now?

I’m not going to abandon this wordpress blog. However, I’m going to tweak the direction of the blog posting on this site. From now on, I will keep technical blog post to the Tinkerer-powered site. By technical, I mean that involves code, mathematical expression, and the content that will be part of my knowledge base doc eventually. However, on the other hand, all the life thoughts and any other non-technical posts will still be kept at this site, especially those posts involve lots of non-technical pictures. zhu45.org will still work as the main portal to my personal site until the new Tinkerer-powered site is GAed.

 

Sqoop2 7 Minutes Demo with DB2

In this post, I’m walking you through Sqoop 1.99.6 5 minutes Demo with emphasis on working with DB2. Hope this post can save you some time on googling (Believe me, I spent quite some time to get everything figured out when doing this hands-on practice on my own.) Let’s get started.

Preparation phase

Installation

I highly recommended to use cloudera hadoop distribution, CDH for this hands-on lab because CDH has already pre-installed Sqoop2 and has everything well-configured. I have tried out sqoop2 installation on  Hortonworks HDP. The experience is not fun. So, I will assume you work with CDH for the rest of walkthrough.

I’m working with cloudera CDH 5.7.0 and “sqoop-1.99.6-bin-hadoop200”.

  1. Once you have sqoop downloaded, you need to untar it on the client that DB2 instance installed.
  2. You need to download the JDBC 4.0 Driver from IBM that matches with your DB2 version.
  3. You need to install the driver onto CDH sqoop and restart the sqoop service:
sudo cp db2jcc4.jar db2jcc.jar /var/lib/sqoop2/
sudo /sbin/service sqoop2-server stop
sudo /sbin/service sqoop2-server start

Create a test database and table in DB2 to work with

$ db2 list db directory

System Database Directory

Number of entries in the directory = 1

Database 1 entry:

Database alias                       = HZY
Database name                        = HZY
Local database directory             = /home/iidev20
Database release level               = 14.00
Comment                              =
Directory entry type                 = Indirect
Catalog database partition number    = 0
Alternate server hostname            =
Alternate server port number         =

$ db2 connect to hzy

  Database Connection Information

Database server        = DB2/LINUXX8664 11.1.0
SQL authorization ID   = IIDEV20
Local database alias   = HZY

$ db2 list tables

Table/View                      Schema          Type  Creation time
------------------------------- --------------- ----- --------------------------
TEST1                           IIDEV20         T     2016-07-12-20.33.38.801694

  1 record(s) selected.

$ db2 "select * from test1"

C1
-----------
          4

  1 record(s) selected.

Creating Link Object

Check for the registered connectors on your sqoop server:

sqoop:000> show connector
+----+------------------------+-----------------+------------------------------------------------------+----------------------+
| Id |          Name          |     Version     |                        Class                         | Supported Directions |
+----+------------------------+-----------------+------------------------------------------------------+----------------------+
| 1  | kite-connector         | 1.99.5-cdh5.7.0 | org.apache.sqoop.connector.kite.KiteConnector        | FROM/TO              |
| 2  | kafka-connector        | 1.99.5-cdh5.7.0 | org.apache.sqoop.connector.kafka.KafkaConnector      | TO                   |
| 3  | hdfs-connector         | 1.99.5-cdh5.7.0 | org.apache.sqoop.connector.hdfs.HdfsConnector        | FROM/TO              |
| 4  | generic-jdbc-connector | 1.99.5-cdh5.7.0 | org.apache.sqoop.connector.jdbc.GenericJdbcConnector | FROM/TO              |
+----+------------------------+-----------------+------------------------------------------------------+----------------------+

Generic JDBC Connector in our example has a persistence Id 4 and we will use this value to create new link object for this connector:

sqoop:000> create link -c 4
Creating link for connector with id 4
Please fill following values to create new link object
Name: Fist Link

Link configuration

JDBC Driver Class: com.ibm.db2.jcc.DB2Driver
JDBC Connection String: jdbc:db2://9.112.250.80:50591/HZY
Username: iidev20
Password: ********
JDBC Connection Properties:
There are currently 0 values in the map:
entry#
New link was successfully created with validation status OK and persistent id 4

There are two things worth mentioning about DB2:

  1. “JDBC Driver Class” for DB2 is “com.ibm.db2.jcc.DB2Driver
  2. In “JDBC Connection String”, I specify the IP address of the client where DB2 instance resides. Then I specify the port number that is for DB2. Lastly I specify the database name we just created.  The port number “50591” can be obtained by:
$ db2 get dbm cfg | grep SVCE
 TCP/IP Service name                          (SVCENAME) = iidev20
 SSL service name                         (SSL_SVCENAME) =

$ grep xiidev20 /etc/services
xiidev20                      50591/tcp
xiidev20_int                  50592/tcp

Our new link object was created with assigned id 4.

Let us create another link object but this time for the hdfs-connector instead:

sqoop:000> create link -c 3
Creating link for connector with id 3
Please fill following values to create new link object
Name: Second Link

Link configuration

HDFS URI: hdfs://quickstart.cloudera:8020/
New link was successfully created with validation status OK and persistent id 5
  1. “quickstart.cloudera” is the hostname, which can be obtained by hostname command in CDH.

Now, we have created two links. You can check by running show link --all:

sqoop:000> show link --all
2 link(s) to show:
link with id 4 and name Fist Link (Enabled: true, Created by iidev20 at 8/4/16 11:57 AM, Updated by iidev20 at 8/4/16 11:57 AM)
Using Connector generic-jdbc-connector with id 4
  Link configuration
      JDBC Driver Class: com.ibm.db2.jcc.DB2Driver
      JDBC Connection String: jdbc:db2://9.112.250.80:50591/HZY
      Username: iidev20
      Password:
      JDBC Connection Properties:
link with id 5 and name Second Link (Enabled: true, Created by iidev20 at 8/4/16 12:36 PM, Updated by iidev20 at 8/4/16 12:36 PM)
Using Connector hdfs-connector with id 3
  Link configuration
    HDFS URI: hdfs://quickstart.cloudera:8020/

Creating Job Object

Now, we create the job object:

sqoop:000> create job -f 4 -t 5
Creating job for links with from id 4 and to id 5
Please fill following values to create new job object
Name: sqoopy

From database configuration

Schema name: IIDEV20
Table name: TEST1
Table SQL statement:
Table column names:
Partition column name: C1
Null value allowed for the partition column:
Boundary query:

ToJob configuration

Override null value:
Null value:
Output format:
  0 : TEXT_FILE
    1 : SEQUENCE_FILE
    Choose: 0
    Compression format:
      0 : NONE
      1 : DEFAULT
      2 : DEFLATE
      3 : GZIP
      4 : BZIP2
      5 : LZO
      6 : LZ4
      7 : SNAPPY
      8 : CUSTOM
    Choose: 0
    Custom compression format:
    Output directory: /cloudera/

    Throttling resources

    Extractors: 2
    Loaders: 2
    New job was successfully created with validation status OK  and persistent id 3

sqoop:000> show job
+----+--------+----------------+--------------+---------+
| Id |  Name  | From Connector | To Connector | Enabled |
+----+--------+----------------+--------------+---------+
| 3  | sqoopy | 4              | 3            | true    |
+----+--------+----------------+--------------+---------+

The idea here is quite simple. We use JDBC connector to read data from our DB2 table (indicated by -f 4), and then we write the data to HDFS by using -t 5.

Here, you need to pay attention to “Partition column name: C1”. If you don’t specify the partition column, you may hit the error when starting the job:

Exception has occurred during processing command
Exception: org.apache.sqoop.common.SqoopException Message: CLIENT_0001:Server has returned exception

Our new job object was created with assigned id 3.

Start Job (Data Transfer)

Now we are ready to start the sqoop job we just created:

sqoop:000> start job -j 3
Submission details
Job ID: 3
Server URL: http://9.181.139.59:12000/sqoop/
Created by: iidev20
Creation date: 2016-08-04 11:57:43 CDT
Lastly updated by: iidev20
External ID: job_1469730693462_0125
        http://quickstart.cloudera:8088/proxy/application_1469730693462_0125/
2016-08-04 11:57:43 CDT: BOOTING  - Progress is not available

You can interatively check your running job status with status job command:

sqoop:000> status job -j 3
Submission details
Job ID: 3
Server URL: http://9.181.139.59:12000/sqoop/
Created by: iidev20
Creation date: 2016-08-04 11:57:43 CDT
Lastly updated by: iidev20
External ID: job_1469730693462_0125
        http://quickstart.cloudera:8088/proxy/application_1469730693462_0125/
2016-08-04 11:58:30 CDT: RUNNING  - 0.00 %

sqoop:000> status job -j 3
Submission details
Job ID: 3
Server URL: http://9.181.139.59:12000/sqoop/
Created by: iidev20
Creation date: 2016-08-04 11:57:43 CDT
Lastly updated by: iidev20
External ID: job_1469730693462_0125
        http://quickstart.cloudera:8088/proxy/application_1469730693462_0125/
2016-08-04 11:58:47 CDT: RUNNING  - 50.00 %

sqoop:000> status job -j 3
Submission details
Job ID: 3
Server URL: http://9.181.139.59:12000/sqoop/
Created by: iidev20
Creation date: 2016-08-04 11:57:43 CDT
Lastly updated by: iidev20
External ID: job_1469730693462_0125
        http://quickstart.cloudera:8088/proxy/application_1469730693462_0125/
2016-08-04 12:00:02 CDT: SUCCEEDED
Counters:
        org.apache.hadoop.mapreduce.FileSystemCounter
                FILE_LARGE_READ_OPS: 0
                FILE_WRITE_OPS: 0
                HDFS_READ_OPS: 1
                HDFS_BYTES_READ: 110
                HDFS_LARGE_READ_OPS: 0
                FILE_READ_OPS: 0
                FILE_BYTES_WRITTEN: 373985
                FILE_BYTES_READ: 17
                HDFS_WRITE_OPS: 2
                HDFS_BYTES_WRITTEN: 2
        org.apache.hadoop.mapreduce.lib.output.FileOutputFormatCounter
                BYTES_WRITTEN: 0
        org.apache.hadoop.mapreduce.lib.input.FileInputFormatCounter
                BYTES_READ: 0
        org.apache.hadoop.mapreduce.JobCounter
                MB_MILLIS_MAPS: 20448256
                TOTAL_LAUNCHED_MAPS: 1
                VCORES_MILLIS_REDUCES: 82496
                TOTAL_LAUNCHED_REDUCES: 2
                VCORES_MILLIS_MAPS: 19969
                SLOTS_MILLIS_REDUCES: 82496
                MB_MILLIS_REDUCES: 84475904
                SLOTS_MILLIS_MAPS: 19969
                MILLIS_REDUCES: 82496
                MILLIS_MAPS: 19969
                OTHER_LOCAL_MAPS: 1
        org.apache.sqoop.submission.counter.SqoopCounters
                ROWS_READ: 1
                ROWS_WRITTEN: 1
                Shuffle Errors
                CONNECTION: 0
                WRONG_LENGTH: 0
                BAD_ID: 0
                WRONG_MAP: 0
                WRONG_REDUCE: 0
                IO_ERROR: 0
         org.apache.hadoop.mapreduce.TaskCounter
                MAP_OUTPUT_MATERIALIZED_BYTES: 17
                MERGED_MAP_OUTPUTS: 2
                SPILLED_RECORDS: 2
                REDUCE_INPUT_RECORDS: 1
                VIRTUAL_MEMORY_BYTES: 4516757504
                MAP_INPUT_RECORDS: 0
                SPLIT_RAW_BYTES: 110
                FAILED_SHUFFLE: 0
                REDUCE_SHUFFLE_BYTES: 17
                MAP_OUTPUT_BYTES: 3
                PHYSICAL_MEMORY_BYTES: 466186240
                GC_TIME_MILLIS: 1305
                REDUCE_INPUT_GROUPS: 1
                COMBINE_OUTPUT_RECORDS: 0
                SHUFFLED_MAPS: 2
                REDUCE_OUTPUT_RECORDS: 1
                MAP_OUTPUT_RECORDS: 1
                COMBINE_INPUT_RECORDS: 0
                CPU_MILLISECONDS: 7470
                COMMITTED_HEAP_BYTES: 393216000
Job executed successfully
  1. start job -j 3 -s allows you to start a sqoop job and observe job running status. stop job -j 3 will not stop running job at any time.

 

Check Result

we can check the result in CDH:

[cloudera@quickstart ~]$ hdfs dfs -ls /cloudera/
Found 2 items
-rw-r--r--   1 sqoop2 supergroup          0 2016-08-04 09:59 /cloudera/33895be5-a670-4e25-aada-a66fc2cf1919.txt
-rw-r--r--   1 sqoop2 supergroup          2 2016-08-04 09:59 /cloudera/ffe359d6-afe9-40e9-baf9-d2e29937a86c.txt
[cloudera@quickstart ~]$ hdfs dfs -cat /cloudera/33895be5-a670-4e25-aada-a66fc2cf1919.txt
[cloudera@quickstart ~]$ hdfs dfs -cat /cloudera/ffe359d6-afe9-40e9-baf9-d2e29937a86c.txt
4

 

Extra 2 Minutes …

In this section, we transfer data back from HDFS to DB2 table:

sqoop:000> create job -f 5 -t 4
Creating job for links with from id 5 and to id 4
Please fill following values to create new job object
Name: h2d

From Job configuration

Input directory: /cloudera/
Override null value:
Null value:

To database configuration

Schema name: IIDEV20
Table name: TEST1
Table SQL statement:
Table column names:
Stage table name:
Should clear stage table:

Throttling resources

Extractors: 2
Loaders: 2
New job was successfully created with validation status OK  and persistent id 4

Then, we start the job:

sqoop:000> start job -j 4 -s
Submission details
Job ID: 4
Server URL: http://9.181.139.59:12000/sqoop/
Created by: iidev20
Creation date: 2016-08-04 14:53:20 CDT
Lastly updated by: iidev20
External ID: job_1469730693462_0134
        http://quickstart.cloudera:8088/proxy/application_1469730693462_0134/
2016-08-04 14:53:20 CDT: BOOTING  - Progress is not available
2016-08-04 14:53:37 CDT: BOOTING  - 0.00 %
2016-08-04 14:53:48 CDT: BOOTING  - 0.00 %
2016-08-04 14:53:58 CDT: BOOTING  - 0.00 %
2016-08-04 14:54:11 CDT: RUNNING  - 0.00 %
2016-08-04 14:54:21 CDT: RUNNING  - 0.00 %
2016-08-04 14:54:32 CDT: RUNNING  - 0.00 %
2016-08-04 14:54:43 CDT: RUNNING  - 0.00 %
2016-08-04 14:54:54 CDT: RUNNING  - 0.00 %
2016-08-04 14:55:05 CDT: RUNNING  - 0.00 %
2016-08-04 14:55:16 CDT: RUNNING  - 50.00 %
2016-08-04 14:55:27 CDT: RUNNING  - 50.00 %
2016-08-04 14:55:38 CDT: RUNNING  - 50.00 %
2016-08-04 14:55:49 CDT: RUNNING  - 50.00 %
2016-08-04 14:55:59 CDT: RUNNING  - 50.00 %
2016-08-04 14:56:10 CDT: RUNNING  - 50.00 %
2016-08-04 14:56:21 CDT: RUNNING  - 100.00 %
2016-08-04 14:56:35 CDT: SUCCEEDED
Counters:
      org.apache.hadoop.mapreduce.FileSystemCounter
              FILE_LARGE_READ_OPS: 0
              FILE_WRITE_OPS: 0
              HDFS_READ_OPS: 8
              HDFS_BYTES_READ: 427
              HDFS_LARGE_READ_OPS: 0
              FILE_READ_OPS: 0
              FILE_BYTES_WRITTEN: 494580
              FILE_BYTES_READ: 17
              HDFS_WRITE_OPS: 0
              HDFS_BYTES_WRITTEN: 0
      org.apache.hadoop.mapreduce.lib.output.FileOutputFormatCounter
              BYTES_WRITTEN: 0
      org.apache.hadoop.mapreduce.lib.input.FileInputFormatCounter
              BYTES_READ: 0
      org.apache.hadoop.mapreduce.JobCounter
              MB_MILLIS_MAPS: 109466624
              TOTAL_LAUNCHED_MAPS: 2
              VCORES_MILLIS_REDUCES: 135729
              TOTAL_LAUNCHED_REDUCES: 2
              VCORES_MILLIS_MAPS: 106901
              SLOTS_MILLIS_REDUCES: 135729
              MB_MILLIS_REDUCES: 138986496
              SLOTS_MILLIS_MAPS: 106901
              MILLIS_REDUCES: 135729
              MILLIS_MAPS: 106901
              OTHER_LOCAL_MAPS: 2
      org.apache.sqoop.submission.counter.SqoopCounters
              ROWS_READ: 1
              ROWS_WRITTEN: 1
              Shuffle Errors
              CONNECTION: 0
              WRONG_LENGTH: 0
              BAD_ID: 0
              WRONG_MAP: 0
              WRONG_REDUCE: 0
              IO_ERROR: 0
      org.apache.hadoop.mapreduce.TaskCounter
              MAP_OUTPUT_MATERIALIZED_BYTES: 29
              MERGED_MAP_OUTPUTS: 4
              SPILLED_RECORDS: 2
              REDUCE_INPUT_RECORDS: 1
              VIRTUAL_MEMORY_BYTES: 6016950272
              MAP_INPUT_RECORDS: 0
              SPLIT_RAW_BYTES: 420
              FAILED_SHUFFLE: 0
              REDUCE_SHUFFLE_BYTES: 29
              MAP_OUTPUT_BYTES: 3
              PHYSICAL_MEMORY_BYTES: 670957568
              GC_TIME_MILLIS: 4852
              REDUCE_INPUT_GROUPS: 1
              COMBINE_OUTPUT_RECORDS: 0
              SHUFFLED_MAPS: 4
              REDUCE_OUTPUT_RECORDS: 1
              MAP_OUTPUT_RECORDS: 1
              COMBINE_INPUT_RECORDS: 0
              CPU_MILLISECONDS: 9510
              COMMITTED_HEAP_BYTES: 453255168
Job executed successfully

Finally, we verify the result from DB2’s perspective:

$ db2 "select * from test1"

C1
-----------
          4
          4

  2 record(s) selected.

Understanding shebang + eval combo from ancient perl script

During the daily development, we have a collection of scripts to help us automate some mundane task. Most of them are written in perl and quite often, I feel shamed to be a programmer that only know how to use those scripts without actually taking a look at their source code. However, I don’t know perl by any measure and recently, I decided to take this challenge: I quickly went through the basic aspects of the language (with the help of this nice tutorial: Learn Perl in about 2 hours 30 minutes )  and dived directly into some scripts to start reading. Then I met this daunting code chunk from the very beginning of a perl script:

#!/usr/bin/perl
eval 'exec perl5 -S $0 ${1+"$@"}'
   if 0;

So, I spent some days digging, and finally get this code chunk clears out. I’ll try my best to explain this code chunk in a newbie-friendly way (because I am one of them :)).

History

Usually, it is unnecessary for people to know that if a script should be executed by Perl, shell, or other interpreters. In other words, they can execute the script by typing the filename and the script itself will find the right interpreter to run it. This is usually done with shebang.  In perl, the common way to do so is to write


#!/usr/bin/perl

at the very first line of your perl script. However, not all system supports shebang and most likely, those systems will run your script as if it is a shell script, which, of course, will lead to the failure of execution. In this case, we need to figure out a way to tell those systems that “even if you are running the script as a shell script, please invoke perl interpreter to interpret the content of the script” and that is exactly what that daunting code does.  Now, let’s dive into this code chunk to see how it works.

Dive in

#!/usr/bin/perl
eval 'exec perl5 -S $0 ${1+"$@"}'
   if 0;

 [1]:  First line of code uses shebang and it invokes perl interpreter located under /usr/bin/, which should be enough  for systems that support shebang to know which perl interpreter should be used to run the content of script.

[2-3]:  For system that support shebang, the system already knows the content of script should be interpreted as perl. So, line 2 – 3 will be treated as perl. Since “Carriage return” is the same as “whitespace” in perl world, line 2 -3 will not get executed because of if 0. However, for system that doesn’t support shebang, the whole script is treated as shell script and thus, line 1 will be treated as shell comment and ignored. Then, the system continues to run line 2 – 3 as shell command. There is one important difference between perl and shell (i.e., bash) is that perl will spot the continuation line (because carriage return is the same as white space) but not bash. So, shell will first execute line 2. For now, let’s just say line 2 executed by shell will tell the system that “to re-run the whole script again under perl and where to find perl interpreter” (we will investigate more in detail in the following section). So now our goal is achieved: system that doesn’t support shebang will go ahead to use specified perl interpreter from line 2 to re-run the whole script as perl script. During the re-run, line 2-3 will get ignored by perl.

Line 2: what the heck is this?

Now, let’s study code from line 2 in detail.

eval 'exec perl5 -S $0 ${1+"$@"}'

The eval in shell takes a string as its argument, and evaluates it as if you’d typed that string on a command line. So, the shell actually executes

exec perl5 -S $0 ${1+"$@"}

$0 get expands to the name of the script by shell. However, ${1+”$@”} looks quite mysterious. It involves an ancient Bourne shell bug (if no argument provided, it uses an empty argument instead of nothing) and the article What does ${1+”@”} mean explains it very clear:

The ${1+"$@"} syntax first tests if $1 is set, that is, if there is an argument at all.
If so, then this expression is replaced with the whole "$@" argument list.
If not, then it collapses to nothing instead of an empty argument.

Aside note on ${1+"$@"}, it follows ${parameter+alt_value} pattern: If parameter set, use alt_value, else use null string. See more on it here.

So now, we can put all pieces together: when shell executes line 2, perl program (i.e. perl5) will be invoked and execute the script itself (expand from $0) and supply the argument list, which may be required by the script.

Example

Let me give an example.

Suppose we have a perl script named foo:

#!/usr/bin/perl
eval 'exec /wsdb/oemtools/linux/bin/perl5.16.2 -S $0 ${1+"$@"}'
    if 0;

use Config;
my $perl = $Config{perlpath};

print $perl."\n";

Besides the daunting code chunk, the rest will print out the absolute path of perl interpreter that our script get executed by. Now, let’s try out different way of executing our perl script foo:

$ perl foo
/usr/bin/perl
$ ./foo
/usr/bin/perl
$ sh foo
/wsdb/oemtools/linuxbin/perl5.16.2

The first two cases, we run the perl script in a standard way, since my system (SUSE Linux 11) supports shebang, the script gets executed by the perl interpreter specified in the shebang line. However, if we try to mimic the system that doesn’t support shebang by executing our script using shell (i.e., sh), the script is also get interpreted as perl script but with the perl interpreter from eval part. Notice sh usage here, sometimes the user of the script may assume the script is written by shell, and they will try to execute the script by sh. Then, in this case, our daunting code chunk provides a defensive mechanism that allows the perl script to be executed correctly even when it is run by shell.

With this explanation, I don’t think code chunk in find2perl will be daunting to you now:

#! /usr/bin/perl -w
    eval 'exec /usr/bin/perl -S $0 ${1+"$@"}'
        if 0; #$running_under_some_shell

 Modern days

Nowadays, people rarely use that daunting code chunk solely because some systems don’t support shebang. Increasingly, that daunting code chunk usually appears when people want to use a specified version of perl (not system default one like /usr/bin/perl) and at the same time, maintain some portability to the system that doesn’t support shebang. However, if we solely consider to use a specified version of perl instead of default one, then there is more than one way to do so. My list may not complete. Please feel free to comment below if I miss some usage on invoking customized perl.

#!/usr/bin/perl
eval 'exec /wsdb/oemtools/linux/bin/perl5.16.2 -S $0 ${1+"$@"}'
    if 0;

The first way is again our daunting code chunk. If we run our script by sh, then our customized perl (i.e., /wsdb/oemtools/linux/bin/perl5.16.2) is executed. -S as perl command option used here is to make perl use PATH environment variable to search for the script because on some system $0 doesn’t always contain the full pathname to the script. You can read more about -S option in perlrun doc and in fact, the daunting code chunk also got explained there.

The second way is to put the following code at the first line of perl script:

#!/wsdb/oemtools/linux/bin/perl5.16.2

This way you directly hardcode the customized perl interpreter in your script. This may sacrifice portability of the script.

Another way to use customized perl interpreter is put this code chunk, again, at the first line of the script:

#!/usr/bin/env perl

This will tell the system (that understands the shebang) to find the first “perl” executable in the list of $PATH. If you want to run your customized perl interpreter this way, you want to put the path to your customized perl interpreter at the beginning of $PATH environment varaible so that you ensure if first “perl” executable found by the system from $PATH is indeed the perl interpreter you want to use.

The last way to run your customized perl interpreter is somewhat similar to our daunting code chunk but with significant difference:

#!/bin/sh
#! -*-perl-*-
eval 'exec /wsdb/oemtools/linux/bin/perl5.16.2 -x -wS $0 ${1+"$@"}'
    if 0;

Let’s run it first to see what we can get. Like previous example section, we put the above code chunk inside a script called bar:

#!/usr/bin/sh
#!-*-perl-*-
eval 'exec /wsdb/oemtools/linux/bin/perl5.16.2 -x -wS $0 ${1+"$@"}'
    if 0;

use Config;
my $perl = $Config{perlpath};

print $perl."\n";
$ bar
/wsdb/oemtools/linuxbin/perl5.16.2
$ perl bar
/wsdb/oemtools/linuxbin/perl5.16.2
$ ./bar
/wsdb/oemtools/linuxbin/perl5.16.2
$ sh bar
/wsdb/oemtools/linuxbin/perl5.16.2

No matter how we execute our script, we always use our customized perl interpreter, even when system perl is explicitly specified (i.e., perl bar). The significant difference than our original daunting code chunk is the use of -x option. The -x does the following:

tells Perl that the program is embedded in a larger chunk of unrelated text, such as in a mail message. Leading garbage will be discarded until the first line that starts with #! and contains the string “perl”. Any meaningful switches on that line will be applied.

Let me walk through what exactly happen in our case. We will use the following information taken from perldoc during the walkthrough as well:

 If the #! line does not contain the word “perl” nor the word “indir”, the program named after the #! is executed instead of the Perl interpreter. This is slightly bizarre, but it helps people on machines that don’t do #! , because they can tell a program that their SHELL is /usr/bin/perl, and Perl will then dispatch the program to the correct interpreter for them.

We launch our bar script as ./bar:

1. Shell executes our script ./bar
2. The system actually executes /bin/sh ./bar because of our shebang specification.
3. sh executes /wsdb/oemtools/linux/bin/perl5.16.2 -x -wS bar
4. /wsdb/oemtools/linux/bin/perl5.16.2 skips:

#!/usr/bin/sh
#!-*-perl-*-
eval 'exec /wsdb/oemtools/linux/bin/perl5.16.2 -x -wS $0 ${1+"$@"}'
    if 0;

and executes:

use Config;
my $perl = $Config{perlpath};

print $perl."\n";

Let’s break down this step into further detail:

4.1 /wsdb/oemtools/linux/bin/perl5.16.2 executes /usr/bin/sh ./bar because it sees a shebang that doesn’t contain the word “perl”
4.2 eval part get executed (i.e., sh executes
/wsdb/oemtools/linux/bin/perl5.16.2 -x -wS bar
)
4.3 Since -x is specified, the first line #!/usr/bin/sh is ignored because it is a shebang but doesn’t contain the string “perl”. Line 2-3 is ignored because if 0. So, the execution starts with use Config; and move forward.

Let’s try launch our bar script using perl bar to see why system perl is not used in this case:

1. Shell executes the script perl bar
2. perl (i.e. /usr/bin/perl) executes /bin/sh bar because it sees a shebang that doesn’t contain the word “perl”
3. eval part get executed (i.e., sh executes
/wsdb/oemtools/linux/bin/perl5.16.2 -x -wS bar
)
4. So our script bar is executed by /wsdb/oemtools/linux/bin/perl5.16.2 instead of /usr/bin/perl

Let’s Practice

Based upon what we learn, you should not have much trouble understanding why

#!/bin/sh
eval 'exec /wsdb/oemtools/linux/bin/perl5.16.2 -wS $0 ${1+"$@"}'
    if 0;

will lead to

/bin/sh: -S: invalid option

error. The key lies in we are not using 1) shebang + string word “perl” and 2) -x option. If you have hard time finding out why, here is the answer.

Thanks for the reading!

 Reference

Book Review: There Is Nothing Wrong with You: Going Beyond Self-Hate

51gdrnmq2ol-_sx321_bo1204203200_

这本书其实我在4,5月份的时候就已经读完了,在从5月份到7月份这期间,甚至我还拿这本书做过一次Toastmaster club speech。如果你有兴趣,这是我当时做演讲用到的slides。之所以拖到这个时间才去写,原因主要有二:

  • 之前两个月我忙着去考GRE
  • 像这种功能书,如果刚读完就去写的话,难免会有概率误人子弟。为啥呢?如果我刚放下书就跑到这里胡扯,而不是亲身按照书上的方法去实践一下的话,这不是误人子弟还能是啥?!(那你为啥还去做演讲呢?这看到本好书难免会激动要去和别人安利一番嘛)
  • 现在时间刚刚好。自己实践了两个月,可以稍微有资格去评价一下书上的观点;脑子里记得关于这本书的感想也行将淡去,是时候付诸文字记录一下了。

缘起

做程序员的多多少少会有一些完美主义。作为土生土长的中国人,这点可能会更为强烈一些。在只许成功不许失败的家庭教育下,在社会上编程大神日益成为网红的时代潮流下,我,一个还算有点上进心的中国程序员,难免会有一些焦虑,自责,乃至自我厌恶——you suck because you’re bad.

终于在某一天的晚上,出于一个记不清的理由(估计是又遭到老妈针对我某件事做法上的批评),我自我厌恶情绪达到了顶点。同时,另外一个从来没出现过的声音出人意料地也在我脑袋里响了起来——老子今晚就要以暴制暴,好好处理一下内心中另外一个我。所以,我想到了之前在reddit上看到的一个post: [Text] Self- loathing is poison. It leads to escapism, procrastination and and depression. Stop focusing on what holds you back. 并想从中获取一些对抗自我厌恶的手段。于是,我就发现了这本书。

详细谈谈这本书

其实你如果不抱着一颗打破沙锅问到底的心态去对self-hate进行全方位剖析的话,这本书给出的针对self-hate的解决之道非常简单,那就是meditation,也就是中文的冥想。如果你能定期(每天一次或者一周一次)去坐下来,慢慢得去聆听自己的呼吸,很多东西就会不然自明。换句话说,你完全可以不用去读这本书,只要去冥想,去接近自己的内心,慢慢的你就会和自己平常所感受到的self-hate达成一种平衡。你就会慢慢的去接受self-hate,接受他的存在,进而做到与之共存,最后将他作为你自己的一部分慢慢吸收进来。正如书中讲到的:

You could just sit down and face a wall and eventually you would understand all of this (what is self-hate, how self-hate is formed …). It’s all available without having any intellectual understanding of it.

但是,如果一本书这样去写,肯定不会有人去读它。因为我们现在处于一个快节奏的社会,人们更希望把时间分配到能立竿见影的事物上面去,如果说一本书告诉人去做冥想就能摆脱self-hate,但并没有告诉人们为什么这个方法会有效的话,我相信很少有人会抱着一种怀疑的态度将书中的方法坚持到底的。所以,书的作者从self-hate的形成原因入手,慢慢地引出mediation,使读者在读完整本书后会有一种完整体系的感觉,从而有信心和动力去贯彻书中所提到的方法。

再细节的地方我不想一一去谈。我就说说这本书给我留下印象比较深的地方。

字体,排版,与插图

下面这张图是我随机从书中挑出来的一页。jfszhxd说实话,我上一次看到这种字体,这种排版,还有这种插图的是在我小学和幼儿园的时候。所以,这本书给我一种亲切,童真的感觉。一种我只要翻翻这本书就会减少很多烦恼的感觉。这也使得我在读这本书的两个月来,每天上班坐上地铁,就会迫不及待的去打开这本书去读。读的时候,书中的字体并没有给人一种严肃说教的感觉。相反的是,你会感觉是一个非常熟识的朋友在一个非常放松的环境下,给你提出的一些真诚的建议。从这个角度上讲,这本书可以非常快得抓住读者的心。

 

 

看问题角度的改变

这本书给我了一种不同的角度去看待self-hate。而看似复杂困难的问题,往往因为角度的转变,解决起来也会变得迎刃而解。比如说self-hate的产生。我过去认为self-hate是一种追求完美的体现,但是我从来没有去深究,self-hate的产生是否是由于追求完美。但是这本书告诉我:self-hate的产生是因为童年并没有被满足的需求。

举个例子来说,假如泽远小的时候特别想要台电脑,但是当我这个请求向父母提出的时候,我得到的回复是”NO”。这个时候,在我的潜意识里就会得到一种判断,那就是: 我的请求之所以没有被父母采纳,是因为我本身不够好,如果我做的更好的话,父母就会给我买电脑。在那个时候,我并没有意识到父母有可能存在问题的可能性(比如他们不给我买电脑并不是因为我做的不够好)。在我的眼里,他们就是权威,而且我的生存也强烈依靠他们。因此,他们不给我买电脑肯定是因为我自身的问题。因此我决定通过不断指责我自己的方式来“追求完美”,从而希望我的需求最后能被父母满足。这种追求完美的心态就渐渐促成了self-hate。

当书中将这件事讲明白的时候,我就突然意识到我平时的一些默认的假设并不是“神圣而不可侵犯”的。比如说,我是否得到父母的认可其实和我本身的完美程度往往并没有直接联系。换句话说,我脑海里越有希望得到他们的认可的这种期望却又得到满足的时候,我下意识就会说我确实不够好,如果我再自己变得完美一些的话,父母就会认可我。可是,我并没有意识到也许父母他们本身并没有存在通过语言等方式去表扬你认可你这种意识。这和你本身是否完美并无关系。即使你做得再好,如果他们没有意识去通过直接的方式认可你的话,你还是依然会陷入自己不够好的死循环中。

明白了这一点,我也就渐渐学会去和父母处关系了。过去,我往往想要指出他们的不足,指出他们应该去多多表扬我,认可我。可是当他们还是保持原有的默不作声的话,我就会感到焦躁。可是,上面这个例子告诉我,我不一定要通过父母去满足我这种需求。比如说我将“你是最好的!”这句话录下来,每天放给自己听。听这句话的本身其实就是一种满足自我需求。而当自我需求被满足后,上面提到的死循环才有可能会被打破。

再引申一步讲,遇到任何困难,比如说表白被拒,找工作不顺,这些往往并不是因为你自己有问题,而是因为这些事本身就是有难度的。换句话说,就是Doing bad at something is not because you’re bad. It is because the thing you are trying to do is hard in itself. 因此,换个角度,就有可能以一个更加积极的态度去看待问题。而不是始终被self-hate所控制,暗无天日,陷入一种消极的状态中。

Too obvious to ignore

我常常会发现有些东西明明摆在那里,但是往往因为它太简单,太不需要去刻意去观察,我就会去忽略它。而被忽略的这个事物却往往会成为解开心结的关键。

书中举了一个非常生动的例子。假如你突然有一天想开始跑步,这时候突然有一个声音蹦出来叫道:“你穿成这样就跑步?你居然敢声称你要跑步。。。” 同时,又有另一个声音向你说道:“你已经做的很好了。就这样跑下去。你跑得非常好,继续加油!”。你仔细想想哪种声音是真正想让你跑步的?而哪种声音是装作给你提建设性意见 让你跑步,但其实是在阻挠你的?

没错,self-hate往往就是以一种建设性意见的姿态出现在你的内心,假装是为了你好,实际上却是在万般阻挠你前进。再试想一下,如果这两种声音不是出自你的心声,而是出自你的两个朋友的话,估计你已经早已离开那个不停向你提出“建设性意见”的朋友远远的了。可是,为什么我们不能对self-hate采取同样的办法呢?仅仅因为self-hate是存活在我们的内心,并没有具体的形态,我们就可以对他听之任之了吗?! 不!

讲到这里,你应该多多少少感受到这本书独特的切入点和看事情的角度了吧。

再多说一句。对于你总给你提“建设性意见”的父母,就因为他们是你的父母,你就应该对他们的意见不假思索的全盘接受了吗?不,因为他们是被他们提“建设性意见”的父母抚养长大的。所以,他们并不知道除了提“建设性意见”,还有什么样的方式来帮助你。现在仔细想想,他们所谓的“建设性意见”有多少是帮助你在人生道路上奔跑,又有多少反倒是泄了你的气,让你每走一小步都觉得无比艰难呢?同理,我曾经幻想去改变我的父母这一点,但是,正如书中所言,如果他们能改,他们早就已经改掉了。只有当我意识到这一点,我反倒能放下我的抵触情绪,以一种包容的心态去看待他们的“建设性意见”。因为我知道,他们是可怜的,他们是被他们的父母以“建设性意见”这一self-hate的表现形式所抚养长大的。

这本书像这样的例子还有很多。我就不一一列举了。最后,我想说,如果你也面对着像我一样被self-hate搞得痛不欲生的话,看看这本书,哪怕你不坚持做meditation,这本书也会改变你看待事物的方式,从而让你获得心灵上的解脱。

 

Quick primer on checking database object privileges in DB2 LUW

db2talk

If you are a DBA, you will inevitably work on troubleshooting/ granting / revoking object privileges to database users. In this blog post, I am going to share how to check for privileges that have been granted to an object in a DB2 LUW database. This post is an introductory level post for new DBAs. Database level authorities are not discussed in this post.

View original post 461 more words