Natural Language Pipeline for Chatbots


Natural Language Pipeline for Chatbots

Chatbot developers usually use two technologies to make the bot understand the meaning of user messages: machine learning andhardcoded rules. See more details on chatbot architecture in my previous article.

Machine learning can help you to identify the intent of the message and extract named entities. It is quite powerful but requires lots of data to train the model. Rule of thumb is to have around 1000 examples for each class for classification problems.

 

If you don’t have enough labeled data then you can handcraft rules which will identify the intent of a message. Rules can be as simple as “if a sentence contains words ‘pay’ and ‘order’ then the user is asking to pay for an order”. And the simplest implementation in your favorite programming language could look like this:

def isRefundRequest(message):
    return 'pay' in message or 'order' in message

Any intent classification code can make errors of two types. True positives: the user doesn’t express an intent, but the chatbot identifies an intent. False positives: the user expresses an intent, but the chatbot doesn’t find it. This simple solution will make lots of errors:

  1. The user can use words “pay” and “order” in different sentences: “I make an order by mistake. I won’t pay.”
  2. A keyword is a substring of another word: “Can I use paypal for order #123?”
  3. Spelling errors: “My orrder number is #123. How can I pay?”
  4. Different forms of words: “How can I pay for my orders?”

 

Your chatbot needs a preprocessing NLP pipeline to handle typical errors. It may include these steps:

  1. Spellcheck

Get the raw input and fix spelling errors. You can do something very simple  or build a spell checker using deep learning.

  1. Split into sentences

It is very helpful to analyze every sentence separately. Splitting the text into sentences is easy, you can use one of NLP libraries, e.g. NLTK, StanfordNLP, SpaCy.

  1. Split into words

This is also very important because hardcoded rules typically operate with words. Same NLP libraries can do it.

  1. POS tagging

Some words have multiple meanings, for an example “charge” as a noun and “charge” as a verb. Knowing a part of speech can help to disambiguate the meaning. You can use same NLP libraries, or Google SyntaxNet, that is a little bit more accurate and supports multiple languages.

  1. Lemmatize words

One word can have many forms: “pay”, “paying”, “paid”. In many cases, an exact form of the word is not important for writing a hardcoded rule. If preprocessing code can identify a lemma, a canonical form of the word, it helps to simplify the rule. Lemmatization, identifying lemmas, is based on dictionaries which list all forms of every word. The most popular dictionary for English is WordNet. NLTK and some other libraries allow using it for lemmatization.

  1. Entity recognition: dates, numbers, proper nouns

Dates and numbers can be expressed in different formats: “3/1/2016″, “1st of March”, “next Wednesday”, “2016-03-01″, “123″, “one hundred”, etc. It may be helpful to convert them to unified format before doing pattern matching. Other entities which require special treatment: locations (countries, regions, cities, street addresses, places), people, phone numbers.

  1. Find concepts/synonyms

If you want to search for a breed of a dog, you don’t want to list all the dog breeds in the rule, because there are hundreds of them. It is nice if preprocessing code identified a dog breed in the message and marked the word with  a special tag. Then you can just look for that tag when applying the rule.

WordNet can be used to identify common concepts. You may need to add domain specific concept libraries, e.g. a list of drug names if you are building a healthcare bot.

 

After preprocessing is done you have a nice clean list of sentences and lists of words inside each sentence. Each word is marked with a part of speech and concepts, and you have a lemma for every word. The next step is to define patterns for intent identification.

You can invent your own pattern language using common logical operators AND, OR, NOT. The rule can look like this if you create an internal DSL (domain-specific language) based on Python:

r = Rule(
    And(
        Or('cancel', 'close'),
        'membership',
    Respond('Would you like to cancel your membership immediately?'))

Alternatively, you can invent external DSL, which can be more readable, but you will need extra work to create a compiler or an interpreter for that language. If you use a ChatScript language, it can look like this:

u: (<<[cancel close] membership>>)
    Would you like to cancel your membership immediately?

Do you use a chatbot engine with hardcoded rules? Have your developed your own? What issues have you encountered when building or using a chatbot engine? Please share in comments!

Categories: life is fun

Nvidia Tesla K20c on Ubuntu 16.04

February 4, 2017 Leave a comment

Spent 2 days to sort this out.  To install Nvidia Tesla K20c on Ubuntu 16.04.

My hardware + OS env :

  • Nvidia Tesla K20c
  • Nvidia GT 520 (graphic card to monitor)
  • Intel i5 CPU
  • 16GB RAM
  • Ubuntu 16.04 on SSD

Initially, I installed the Nvidia official driver, and CUDA using :

  • sudo apt-get install nvidia-cuda-toolkit
  • sudo ./NVIDIA-Linux-x86_64-331.89.run

But then ran into several weird errors like below.

“`
victor@ubuntu-tesla:~/Downloads$ cat /var/log/nvidia-installer.log
nvidia-installer log file ‘/var/log/nvidia-installer.log’
creation time: Sat Feb 4 11:15:23 2017
installer version: 331.89

PATH: /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/snap/bin

nvidia-installer command line:
./nvidia-installer
–no-cc-version-check

Using: nvidia-installer ncurses user interface
-> License accepted.
-> Installing NVIDIA driver version 331.89.
-> There appears to already be a driver installed on your system (version: 331.89). As part of installing this driver (version: 331.89), the existing driver will be uninstalled. Are you sure you want to continue? (‘no’ will abort installation) (Answer: Yes)
-> Running distribution scripts
executing: ‘/usr/lib/nvidia/pre-install’…
-> done.
-> The distribution-provided pre-install script failed! Continue installation anyway? (Answer: Yes)
-> Would you like to register the kernel module sources with DKMS? This will allow DKMS to automatically build a new module, if you install a different kernel later. (Answer: No)
-> Performing CC sanity check with CC=”cc”.
-> Kernel source path: ‘/lib/modules/4.4.0-59-generic/build’
-> Kernel output path: ‘/lib/modules/4.4.0-59-generic/build’
-> Performing rivafb check.
-> Performing nvidiafb check.
-> Performing Xen check.
-> Performing PREEMPT_RT check.
-> Cleaning kernel module build directory.
executing: ‘cd ./kernel; make clean’…
-> Building NVIDIA kernel module:
executing: ‘cd ./kernel; make module SYSSRC=/lib/modules/4.4.0-59-generic/build SYSOUT=/lib/modules/4.4.0-59-generic/build NV_BUILD_MODULE_INSTANCES=’…
NVIDIA: calling KBUILD…
make[1]: Entering directory ‘/usr/src/linux-headers-4.4.0-59-generic’
test -e include/generated/autoconf.h -a -e include/config/auto.conf || ( \
echo >&2; \
echo >&2 ” ERROR: Kernel configuration is invalid.”; \
echo >&2 ” include/generated/autoconf.h or include/config/auto.conf are missing.”;\
echo >&2 ” Run ‘make oldconfig && make prepare’ on kernel src to fix it.”; \
echo >&2 ; \
/bin/false)
mkdir -p /tmp/selfgz26270/NVIDIA-Linux-x86_64-331.89/kernel/.tmp_versions ; rm -f /tmp/selfgz26270/NVIDIA-Linux-x86_64-331.89/kernel/.tmp_versions/*
make -f ./scripts/Makefile.build obj=/tmp/selfgz26270/NVIDIA-Linux-x86_64-331.89/kernel
cc -Wp,-MD,/tmp/selfgz26270/NVIDIA-Linux-x86_64-331.89/kernel/.nv.o.d -nostdinc -isystem /usr/lib/gcc/x86_64-linux-gnu/5/include -I./arch/x86/include -Iarch/x86/include/generated/uapi -Iarch/x86/include/generated -Iinclude -I./arch/x86/include/uapi -Iarch/x86/include/generated/uapi -I./include/uapi -Iinclude/generated/uapi –
include ./include/linux/kconfig.h -Iubuntu/include -D__KERNEL__ -fno-pie -Wall -Wundef -Wstrict-prototypes -Wno-trigraphs -fno-strict-aliasing -fno-common -Werror-implicit-function-declaration -Wno-format-security -std=gnu89 -fno-PIE -fno-pie -no-pie -mno-sse -mno-mmx -mno-sse2 -mno-3dnow -mno-avx -m64 -falign-jumps=1 -falign-loops=1 -mno-80387 -mno-fp-ret-in-387 -mpreferred-stack-boundary=3 -mskip-rax-setup -mtune=generic -mno-red-zone -mcmodel=kernel -funit-at-a-time -maccumulate-outgoing-args -DCONFIG_X86_X32_ABI -DCONFIG_AS_CFI=1 -DCONFIG_AS_CFI_SIGNAL_FRAME=1 -DCONFIG_AS_CFI_SECTIONS=1 -DCONFIG_AS_FXSAVEQ=1 -DCONFIG_AS_SSSE3=1 -DCONFIG_AS_CRC32=1 -DCONFIG_AS_AVX=1 -DCONFIG_AS_AVX2=1 -DCONFIG_AS_SHA1_NI=1 -DCONFIG_AS_SHA256_NI=1 -pipe -Wno-sign-compare -fno-asynchronous-unwind-tables -fno-delete-null-pointer-checks -Wno-maybe-uninitialized -O2 –param=allow-store-data-races=0 -Wframe-larger-than=1024 -fstack-protector-strong -Wno-unused-but-set-variable -fno-omit-frame-pointer -fno-optimize-sibling-calls
-fno-var-tracking-assignments -pg -mfentry -DCC_USING_FENTRY -Wdeclaration-after-statement -Wno-pointer-sign -fno-strict-overflow -fconserve-stack -Werror=implicit-int -Werror=strict-prototypes -Werror=date-time -DCC_HAVE_ASM_GOTO -DNV_MODULE_INSTANCE=0 -DNV_BUILD_MODULE_INSTANCES=0 -UDEBUG -U_DEBUG -DNDEBUG -I/tmp/selfgz26270/NVIDIA-Linux-x86_64-331.89/kernel -Wall -MD -Wsign-compare -Wno-cast-qual -Wno-error -D__KERNEL__ -DMODULE -DNVRM -DNV_VERSION_STRING=\”331.89\” -Wno-unused-function -Wuninitialized -mno-red-zone -mcmodel=kernel -DNV_UVM_ENABLE -D__linux__ -DNV_DEV_NAME=\”nvidia\” -DMODULE -D”KBUILD_STR(s)=#s” -D”KBUILD_BASENAME=KBUILD_STR(nv)” -D”KBUILD_MODNAME=KBUILD_STR(nvidia)” -c -o /tmp/selfgz26270/NVIDIA-Linux-x86_64-331.89/kernel/.tmp_nv.o /tmp/selfgz26270/NVIDIA-Linux-x86_64-331.89/kernel/nv.c
In file included from include/uapi/linux/stddef.h:1:0,
from include/linux/stddef.h:4,
from ./include/uapi/linux/posix_types.h:4,
from include/uapi/linux/types.h:13,
from include/linux/types.h:5,
from include/uapi/linux/capability.h:16,
from include/linux/capability.h:15,
from include/linux/sched.h:15,
from include/linux/utsname.h:5,
from /tmp/selfgz26270/NVIDIA-Linux-x86_64-331.89/kernel/nv-linux.h:44,
from /tmp/selfgz26270/NVIDIA-Linux-x86_64-331.89/kernel/nv.c:13:
include/asm-generic/qrwlock.h: In function ‘queued_write_trylock’:
include/asm-generic/qrwlock.h:93:36: warning: comparison between signed and unsigned integer expressions [-Wsign-compare]
cnts, cnts | _QW_LOCKED) == cnts);
^
include/linux/compiler.h:165:40: note: in definition of macro ‘likely’
# define likely(x) __builtin_expect(!!(x), 1)
^
In file included from ./arch/x86/include/asm/preempt.h:5:0,
from include/linux/preempt.h:59,
from include/linux/spinlock.h:50,
from include/linux/seqlock.h:35,
from include/linux/time.h:5,
from include/uapi/linux/timex.h:56,
from include/linux/timex.h:56,
from include/linux/sched.h:19,
from include/linux/utsname.h:5,
from /tmp/selfgz26270/NVIDIA-Linux-x86_64-331.89/kernel/nv-linux.h:44,
from /tmp/selfgz26270/NVIDIA-Linux-x86_64-331.89/kernel/nv.c:13:
include/linux/percpu-refcount.h: In function ‘percpu_ref_get_many’:
./arch/x86/include/asm/percpu.h:130:31: warning: comparison between signed and unsigned integer expressions [-Wsign-compare]
((val) == 1 || (val) == -1)) ? \
^
./arch/x86/include/asm/percpu.h:419:34: note: in expansion of macro ‘percpu_add_op’
#define this_cpu_add_1(pcp, val) percpu_add_op((pcp), val)
^
include/linux/percpu-defs.h:364:11: note: in expansion of macro ‘this_cpu_add_1’
case 1: stem##1(variable, __VA_ARGS__);break; \
^
include/linux/percpu-defs.h:496:33: note: in expansion of macro ‘__pcpu_size_call’
#define this_cpu_add(pcp, val) __pcpu_size_call(this_cpu_add_, pcp, val)
^
include/linux/percpu-refcount.h:177:3: note: in expansion of macro ‘this_cpu_add’
this_cpu_add(*percpu_count, nr);
^
./arch/x86/include/asm/percpu.h:130:31: warning: comparison between signed and unsigned integer expressions [-Wsign-compare]
((val) == 1 || (val) == -1)) ? \
^
./arch/x86/include/asm/percpu.h:420:34: note: in expansion of macro ‘percpu_add_op’
#define this_cpu_add_2(pcp, val) percpu_add_op((pcp), val)
^
include/linux/percpu-defs.h:365:11: note: in expansion of macro ‘this_cpu_add_2’
case 2: stem##2(variable, __VA_ARGS__);break; \
^
include/linux/percpu-defs.h:496:33: note: in expansion of macro ‘__pcpu_size_call’
#define this_cpu_add(pcp, val) __pcpu_size_call(this_cpu_add_, pcp, val)
^
include/linux/percpu-refcount.h:177:3: note: in expansion of macro ‘this_cpu_add’
this_cpu_add(*percpu_count, nr);
^
./arch/x86/include/asm/percpu.h:130:31: warning: comparison between signed and unsigned integer expressions [-Wsign-compare]
((val) == 1 || (val) == -1)) ? \
^
./arch/x86/include/asm/percpu.h:421:34: note: in expansion of macro ‘percpu_add_op’
#define this_cpu_add_4(pcp, val) percpu_add_op((pcp), val)
^
include/linux/percpu-defs.h:366:11: note: in expansion of macro ‘this_cpu_add_4’
case 4: stem##4(variable, __VA_ARGS__);break; \
^
include/linux/percpu-defs.h:496:33: note: in expansion of macro ‘__pcpu_size_call’
#define this_cpu_add(pcp, val) __pcpu_size_call(this_cpu_add_, pcp, val)
^
include/linux/percpu-refcount.h:177:3: note: in expansion of macro ‘this_cpu_add’
this_cpu_add(*percpu_count, nr);
^
./arch/x86/include/asm/percpu.h:130:31: warning: comparison between signed and unsigned integer expressions [-Wsign-compare]
((val) == 1 || (val) == -1)) ? \
^
./arch/x86/include/asm/percpu.h:478:35: note: in expansion of macro ‘percpu_add_op’
#define this_cpu_add_8(pcp, val) percpu_add_op((pcp), val)
^
include/linux/percpu-defs.h:367:11: note: in expansion of macro ‘this_cpu_add_8’
case 8: stem##8(variable, __VA_ARGS__);break; \
^
include/linux/percpu-defs.h:496:33: note: in expansion of macro ‘__pcpu_size_call’
#define this_cpu_add(pcp, val) __pcpu_size_call(this_cpu_add_, pcp, val)
^
include/linux/percpu-refcount.h:177:3: note: in expansion of macro ‘this_cpu_add’
this_cpu_add(*percpu_count, nr);
^
include/linux/percpu-refcount.h: In function ‘percpu_ref_put_many’:
./arch/x86/include/asm/percpu.h:130:31: warning: comparison between signed and unsigned integer expressions [-Wsign-compare]
((val) == 1 || (val) == -1)) ? \
^
./arch/x86/include/asm/percpu.h:419:34: note: in expansion of macro ‘percpu_add_op’
#define this_cpu_add_1(pcp, val) percpu_add_op((pcp), val)
^
include/linux/percpu-defs.h:364:11: note: in expansion of macro ‘this_cpu_add_1’
case 1: stem##1(variable, __VA_ARGS__);break; \
^
include/linux/percpu-defs.h:496:33: note: in expansion of macro ‘__pcpu_size_call’
#define this_cpu_add(pcp, val) __pcpu_size_call(this_cpu_add_, pcp, val)
^
include/linux/percpu-defs.h:506:33: note: in expansion of macro ‘this_cpu_add’
#define this_cpu_sub(pcp, val) this_cpu_add(pcp, -(typeof(pcp))(val))
^
include/linux/percpu-refcount.h:276:3: note: in expansion of macro ‘this_cpu_sub’
this_cpu_sub(*percpu_count, nr);
^
./arch/x86/include/asm/percpu.h:130:31: warning: comparison between signed and unsigned integer expressions [-Wsign-compare]
((val) == 1 || (val) == -1)) ? \
^
./arch/x86/include/asm/percpu.h:420:34: note: in expansion of macro ‘percpu_add_op’
#define this_cpu_add_2(pcp, val) percpu_add_op((pcp), val)
^
include/linux/percpu-defs.h:365:11: note: in expansion of macro ‘this_cpu_add_2’
case 2: stem##2(variable, __VA_ARGS__);break; \
^
include/linux/percpu-defs.h:496:33: note: in expansion of macro ‘__pcpu_size_call’
#define this_cpu_add(pcp, val) __pcpu_size_call(this_cpu_add_, pcp, val)
^
include/linux/percpu-defs.h:506:33: note: in expansion of macro ‘this_cpu_add’
#define this_cpu_sub(pcp, val) this_cpu_add(pcp, -(typeof(pcp))(val))
^
include/linux/percpu-refcount.h:276:3: note: in expansion of macro ‘this_cpu_sub’
this_cpu_sub(*percpu_count, nr);
^
./arch/x86/include/asm/percpu.h:130:31: warning: comparison between signed and unsigned integer expressions [-Wsign-compare]
((val) == 1 || (val) == -1)) ? \
^
./arch/x86/include/asm/percpu.h:421:34: note: in expansion of macro ‘percpu_add_op’
#define this_cpu_add_4(pcp, val) percpu_add_op((pcp), val)
^
include/linux/percpu-defs.h:366:11: note: in expansion of macro ‘this_cpu_add_4’
case 4: stem##4(variable, __VA_ARGS__);break; \
^
include/linux/percpu-defs.h:496:33: note: in expansion of macro ‘__pcpu_size_call’
#define this_cpu_add(pcp, val) __pcpu_size_call(this_cpu_add_, pcp, val)
^
include/linux/percpu-defs.h:506:33: note: in expansion of macro ‘this_cpu_add’
#define this_cpu_sub(pcp, val) this_cpu_add(pcp, -(typeof(pcp))(val))
^
include/linux/percpu-refcount.h:276:3: note: in expansion of macro ‘this_cpu_sub’
this_cpu_sub(*percpu_count, nr);
^
./arch/x86/include/asm/percpu.h:130:31: warning: comparison between signed and unsigned integer expressions [-Wsign-compare]
((val) == 1 || (val) == -1)) ? \
^
./arch/x86/include/asm/percpu.h:478:35: note: in expansion of macro ‘percpu_add_op’
#define this_cpu_add_8(pcp, val) percpu_add_op((pcp), val)
^
include/linux/percpu-defs.h:367:11: note: in expansion of macro ‘this_cpu_add_8’
case 8: stem##8(variable, __VA_ARGS__);break; \
^
include/linux/percpu-defs.h:496:33: note: in expansion of macro ‘__pcpu_size_call’
#define this_cpu_add(pcp, val) __pcpu_size_call(this_cpu_add_, pcp, val)
^
include/linux/percpu-defs.h:506:33: note: in expansion of macro ‘this_cpu_add’
#define this_cpu_sub(pcp, val) this_cpu_add(pcp, -(typeof(pcp))(val))
^
include/linux/percpu-refcount.h:276:3: note: in expansion of macro ‘this_cpu_sub’
this_cpu_sub(*percpu_count, nr);
^
In file included from include/uapi/linux/stddef.h:1:0,
from include/linux/stddef.h:4,
from ./include/uapi/linux/posix_types.h:4,
from include/uapi/linux/types.h:13,
from include/linux/types.h:5,
from include/uapi/linux/capability.h:16,
from include/linux/capability.h:15,
from include/linux/sched.h:15,
from include/linux/utsname.h:5,
from /tmp/selfgz26270/NVIDIA-Linux-x86_64-331.89/kernel/nv-linux.h:44,
from /tmp/selfgz26270/NVIDIA-Linux-x86_64-331.89/kernel/nv.c:13:
./arch/x86/include/asm/uaccess.h: In function ‘copy_from_user’:
./arch/x86/include/asm/uaccess.h:717:26: warning: comparison between signed and unsigned integer expressions [-Wsign-compare]
if (likely(sz < 0 || sz >= n))
^
include/linux/compiler.h:165:40: note: in definition of macro ‘likely’
# define likely(x) __builtin_expect(!!(x), 1)
^
./arch/x86/include/asm/uaccess.h: In function ‘copy_to_user’:
./arch/x86/include/asm/uaccess.h:735:26: warning: comparison between signed and unsigned integer expressions [-Wsign-compare]
if (likely(sz < 0 || sz >= n))
^
include/linux/compiler.h:165:40: note: in definition of macro ‘likely’
# define likely(x) __builtin_expect(!!(x), 1)
^
/tmp/selfgz26270/NVIDIA-Linux-x86_64-331.89/kernel/nv.c: In function ‘nvidia_unlocked_ioctl’:
/tmp/selfgz26270/NVIDIA-Linux-x86_64-331.89/kernel/nv.c:2027:29: error: ‘struct file’ has no member named ‘f_dentry’
return nvidia_ioctl(file->f_dentry->d_inode, file, cmd, i_arg);
^
scripts/Makefile.build:258: recipe for target ‘/tmp/selfgz26270/NVIDIA-Linux-x86_64-331.89/kernel/nv.o’ failed
make[2]: *** [/tmp/selfgz26270/NVIDIA-Linux-x86_64-331.89/kernel/nv.o] Error 1
Makefile:1420: recipe for target ‘_module_/tmp/selfgz26270/NVIDIA-Linux-x86_64-331.89/kernel’ failed
make[1]: *** [_module_/tmp/selfgz26270/NVIDIA-Linux-x86_64-331.89/kernel] Error 2
make[1]: Leaving directory ‘/usr/src/linux-headers-4.4.0-59-generic’
NVIDIA: left KBUILD.
nvidia.ko failed to build!
Makefile:178: recipe for target ‘nvidia.ko’ failed
make: *** [nvidia.ko] Error 1
-> Error.
ERROR: Unable to build the NVIDIA kernel module.
ERROR: Installation has failed. Please see the file ‘/var/log/nvidia-installer.log’ for details. You may find suggestions on fixing installation problems in the README available on the Linux driver download page at http://www.nvidia.com.

“`

 

The solution is the DKMS !

“`

sudo dkms remove nvidia-current-updates/331.89

sudo apt-get install nvidia-current-updates

“`

Then re-install them:

“`

sudo ./NVIDIA-Linux-x86_64-331.89.run
sudo apt-get install nvidia-cuda-toolkit

“`

Ta-da! all works!!!!!!

“`

victor@ubuntu-tesla:~/$ nvidia-smi
Sat Feb 4 12:11:39 2017
+—————————————————————————–+
| NVIDIA-SMI 367.57 Driver Version: 367.57 |
|——————————-+———————-+———————-+
| GPU Name Persistence-M| Bus-Id Disp.A | Volatile Uncorr. ECC |
| Fan Temp Perf Pwr:Usage/Cap| Memory-Usage | GPU-Util Compute M. |
|===============================+======================+======================|
| 0 Tesla K20c Off | 0000:01:00.0 Off | 0 |
| 30% 35C P8 27W / 225W | 0MiB / 4742MiB | 0% Default |
+——————————-+———————-+———————-+
| 1 GeForce GT 520 Off | 0000:02:00.0 N/A | N/A |
| 40% 40C P0 N/A / N/A | 348MiB / 955MiB | N/A Default |
+——————————-+———————-+———————-+

+—————————————————————————–+
| Processes: GPU Memory |
| GPU PID Type Process name Usage |
|=============================================================================|
| 1 Not Supported |
+—————————————————————————–+
“`

 

 

Categories: life is fun

关于lease bmw的好处,和具体操作

October 1, 2016 Leave a comment

关于lease bmw的好处,和具体操作

先说结论: 如果不是要开的里程特别高,比如一年两三万mile这种

或者是要keep一辆车很久而且自己还会动手修车

否则lease bmw是经济上和体验上都更好的选择,相对购买.

下面说原因.

首先大方向上,如果自己不会修车,bmw不算是适合长期持有的车,

因为整体上一方面它的可靠性只算中规中矩,另一方面它不是很repair friendly

那么意味着出了保修,无论是必须要进行的保养,还是可能出现的故障及维修

都不是一笔小花销.

而如果购买延保,quote出来的原厂warranty+maintenance价格相当不菲,

回头买了你车不坏,纠结,没买车坏了一笔几千块账单跳出来,也纠结

最后一算出保后平均每年的cost to own,恐怕没比再lease个新车省多少

另一方面,在保修期内的bmw则非常省心,因为它是同时包warranty和maintenance,

可以说除了胎,别的上不用花什么钱

所以很多买家选择把车在过保前处理掉

那么如果确定车到手不会keep超过保修,面临的就是买来三年多卖掉,

还是lease三年.

这里面lease的优势是很明显的,体现在以下几个方面:

1.只有车的使用权,而不拥有车.这意味着不用对车特别照顾,有些轻微磕碰也不用在乎.

而且比方说一旦车出现不大不小的事故,有carfax记录但又没到报废的程度,

也不用担心以后不好卖或者卖不上价.

2.bmw和很多豪华车都算上,精髓很多是在选配里面.但这些选配有一个问题,就是你

到卖的时候,卖不上价.比方说55k起价的x5,你配到70k算是well equipped,

但三年后卖,很可能你70k的x5,也就比55k的乞丐版x5多卖五千块钱,

这意味着选配对应的售价部分会有超过车起价以上的更高折旧.

问题在于买豪华车本来就是图个体验好,弄个没啥选配的乞丐版,那体验也要大打折扣.

而在lease的时候,情况则完全不同.

首先lease的残值百分比是作用在整个车价上,意味着选配部分

对应的车价按照起价相同的比例计算折旧.其次高配的车通常更好砍价,意味着

实际上按成交价看,高配车相对乞丐版的溢价就要比两者msrp差价更低.

结果就是如果lease车,选配部分的cost to own比buy and sell的情况低很多.

3.以bmw而言,其设定的残值比例通常是比你三年后卖掉能卖出来的价高的,

且不说lease三年后直接把车还掉,不用再折腾自己卖车(费时间)或者再多被

dealer砍一刀(tradein亏钱).

综合以上三点,lease bmw,当lease是个选择的时候(有时候不是选择,比如如果

你要一年开两三万mile的话,那通常lease没这么高里程的选项),它常常是那个

更好的选择.

那么接下来说说bmw lease的基本情况和计算方法.

lease一辆车,其成本主要来自三个方面:

1. lease acquisition fee, doc fee, tax

像bmw的acq fee在795,doc fee取决于具体dealer, tax取决于具体州

这里面acq fee和tax是固定的,doc fee则可以视情况和dealer砍

2.车辆折旧,也就是车在lease期间产生的贬值.

其计算方法就是成交价-lease到期残值百分比*MSRP.

比方说我一辆70k msrp的x5,三年36kmile残值是60%,成交价65k,

那么折旧就是65-70*0.6=23k

3.财务成本.lease期间车的所有权在车厂的财务部门,那么对方持有这个车辆会占款,

因此也就会产生财务成本.这个的计算,是用成交价和到期残值的均值,

结合当前利率算出每月的财务成本.

举一例,比方说现在的apr是3.12%,那么每月的财务成本就是

(成交价+残值)/2*(3.12%/12)=(成交价+残值)*(3.12%/24)

而这里3.12%/24这一项就被命名为money factor

那么实际上1是会打到2的成交价中,所以进一步可以说,

最后决定整个lease期间支出的,就是折旧及财务支出.

对于bmw来说,对于某个型号,某段时间内它会确定一个残值百分比,

比如15款x5目前就是3年36k残值60%.而money factor也是相对固定的,

目前是0.0013(对应的apr则是3.12%)

那么实际上可以和dealer商定的就是成交价这一项(也包含doc fee).

需要提及的是,bmw官网,乃至dealer店里,常常会贴出来一个广告,

说xxxx down, yyy/month for zz (通常是30/36) month.

实际上这组数字,就是基于当前的money factor和残值设定,以及一个bmw

设定的成交价来计算出来的.而这成交价多半是不太厚道的.所以切记不要直接

从了这个lease term.如果要看某个车的当前残值设定是多少,可以直接去

bmwusa官网点special offer->lease offer.对目标车型,点进去之后看

lease offer下面的小字部分,里面都会给出说本lease offer基于msrp xxxxx,

到期残值yyyyy进行计算.用yyyyy/xxxxx,就能得到这车型当前的残值百分比.

前面已经说了,lease的折旧部分,成交价用的是实际价格,而残值百分比是作用在

msrp上,也就是说当看好了一款车,那么对应的残值就固定了.成交价砍的越低,

折旧就越少.而且这里面的成交价砍价有杠杆效应:还是以x5为例,三年残值60%,

意味着如果成交价从msrp砍到10%off msrp,那么折旧支出就从40%*msrp减到

30%*msrp,也即是说降低25%.那么如果再遇到bmw某些时候搞活动,弄出来一个

非常高的残值百分比,而车价又砍掉比较多,那么最后的折旧就会低到令人发指的程度.

比方说前一阵子bmw对15款x1给到3年68%的残值,而x1通常能搞到10%off或更高,

那么这车三年只需要付出22%msrp就能搞定,意味着一辆40k的x1,三年折旧只有8.8k

平均到每月只有244.44usd,这就是超好的deal了.

另一方面是财务成本部分.显然的,砍成交价意味着可以降低money factor作用的本金,

因此有额外的好处.但更好的一点就是bmw有一个monthly security deposit(msd)

的政策,也就是说每压一个monthly payment的钱给bmw financial services,

就可以把money factor降低0.00007,最多可以压7个.这个钱在lease结束后会退回.

那么压7个之后,原来0.0013的mf会降到0.00081,也就是等效apr从3.12降到1.94,

或者更直观的,可以将财务成本支出降低37.7%,这也相当可观.

还是大致算一笔账,70k的x5,65k成交,60%残值,那么暂时忽略手续费,

每月的财务成本,原本是(65k+42k)*0.0013=139.1,7个msd后是86.67,

意味着36个月下来可以省1887.48usd.

那么同时大致计算一个msd是(65k-42k)/36+139.1=778

意味着5446usd带来的三年回报是1887.48,以单利计算年回报率在11.6%,

这是很划算的买卖,所以基本上7个msd是lease bmw必搞的.

最后,关于lease payment.bmw广告里常常会说要downpay三千多再每个月付多少

但实际上更合适的选择是0 downpay,直接每月付款.

这里主要顾虑是一旦出了一些意外事件,比如你要转lease,或者车total了

那么downpay的部分你是拿不回来的,白白损失了.所以宁可每个月按月支付折旧.

更新:以下是cassis网友在本线程下的回复,关于lease downpay的

很有信息量,所以全文引用如下:

发信人: cassis (KW), 信区: Automobile
标  题: Re: 稍微多说几句关于lease bmw的好处,和具体操作 (转载)
发信站: BBS 未名空间站 (Sat Mar 21 03:16:50 2015, 美东)

我觉得他说的大体上是对的,至少中心思想上。

Lease的车total掉的话,原则上你欠car company的钱相当于early buyout。这个early
buyout具体怎么算我不是很清楚,应该不完全是随时间线性递减的。保险公司陪的钱
比这个early buyout少得话,有GAP insurance的话由GAP insurance负责差价,否则是
你付。

举个极端例子吧,你lease个5万卖价的车,开出去第一天就total掉。假设没有down
payment,这时候的early buyout就应该是5万。保险公司多半不会按新车全价陪,假如
它只陪4万5的话,你(或者GAP)陪剩余的5千。

假如你事先付了5千首付,这时候early buyout应该是4万5,刚好和保险公司陪的一样
,所以GAP也不会启用。这种情况下你开了一天车就完全损失掉5千首付。这就是为什么
lease一般不推荐down payment(注意trade-in也相当于down payment)。GAP
insurance一般都是包在lease的acquisition fee里的,所以total了应该可以拍屁股走
人。

对BMW的lease,补充几点:

1) Acquisition fee is $725 standard, but the dealer is allowed to mark it up
to $925. This is $200 extra profit for the dealer.
2) Money factor is 0.0013 standard (at least for much of the time). Again
the dealer is allowed to mark it up by 0.0004 to 0.0017. Multiply it by 2400
you will know this means 1% higher interest. If you lease a $50K car, this
means roughly an extra $400/year on interest, or $1200 total for a 3 year
lease. This is a common trick by the dealer. You should absolutely insist on
the standard MF.
3) There is also a $300 disposition fee when you return the car at the lease
end. It can be waived if you lease another BMW.
4) Not sure if there is also a buyout fee. I won’t buy out at the end (as it
defeats the purpose of leasing) so I didn’t do much research. If you do buy
out at the end, then you paid extra money (acquisition + buyout fees)
compared to buy directly at the beginning.

对于上述帖子,我的推论就是,当车total,如果是0 downpay,total时的保险公司

赔付和尚未支付的cap cost与到期残值之和(也就是上文中的“early buyout value”)

之间有差距(后者大于前者),这个差距也就是所谓的gap

则acquisition fee中包含的gap insurance负责付这个gap,lease车主不需额外付款。

而如果downpay付的多,本质上相当于downpay的全部或部分来支付上述gap,

结果就是很可能已经付掉的downpay也拿不回来,因此带来额外的损失。

基于上述考虑,选择0 downpay可以规避上述风险,因此是更好的选择。

最后,关于如何和dealer周旋.

和买车不同,很多人对lease的价格构成和计算是不太熟悉的,那么dealer因此也习惯

去宰lease客户,这带来的一个副产品就是对于lease客户,dealer默认更不愿意妥协,

因为会认为对方好忽悠.所以更好的方法,如@Leasing所说,去dealer先别提lease的事,

事先把功课做好,公式放手机里备用,直接就按买车来砍价.同时,如果手里有什么

厂方给的折扣,比如test drive credit, new graduate credit,等等,一律握住先不要提

等把价格砍好了,再说要按这个价格lease,并且提出压msd降mf,然后如果遇到doc fee

收的离谱的,再往下砍.到最后要成交打单子了,再把厂方给的credit提出来要求再减,

否则无良dealer会把这部分折扣直接当成自己给的来议价,增加砍价难度.

不小心敲了一大篇,已经记不得前面都说了什么,先发这些,

大伙如果有问题再回复,我试着解答.

另外我手里有带我入门的哥们儿发给我的excel,据说是完全复制了dealer的lease计算
程序

有兴趣的朋友可以回复留个邮箱,我跟大家分享一下:)

————————-
5月19日更新:

还有一点需要补充的好处,是消费税上的优势。

在那些不需对lease车进行全额缴纳消费税(比如德州就如此规定)的州

消费税仅作用在downpay和monthly payment上

也即是说残值部分不需要交税。

而对应无论cash还是finance购买,都要对车的全款进行缴税。

以一辆65k的x5来看,如果按60%残值,6%消费税计算,则意味着节省2340usd的税

而在消费税比较高的州,比如消费税到了9%左右,则可节省更多(3510usd)

当然,如果车去tradein,那么也能得到买下一辆车的消费税抵扣,

但通常tradein给dealer时,对方给的offer都会偏低。如果卖给私人,

则无法得到消费税抵扣。

Categories: life is fun

如何通过房屋租售比来判断房产的价值或泡沫?

October 1, 2016 Leave a comment

链接:https://www.zhihu.com/question/20799544/answer/44545583
来源:知乎
著作权归作者所有,转载请联系作者获得授权。

【4月14日的更新】
感谢 @chenqin老师对售租比数据取值范围的建议,我们前后各去除了2%之后,那些比较奇葩的售租比超过2000的值(估计是挂牌价错误或单位宿舍之类的)被剔除出去了。相应的部分图纸也进行了更新,其中那张散点图的趋势变得更为明显,售租比离散区从距市中心20km处即显现了出来。
—————————————————————————————————————————————–
【4月12日的原文】
最近一直和一位师兄在研究上海的房价,有点初步的结果,拿出来跟大家讨论一下。内容有点长,先说结论吧:

就上海而言,观察所谓“房价泡沫”的问题,房屋租售比在空间上的离散程度(而非其数值)有可能是一个更好的视角。

具体内容如下:

房屋租售比是什么?一般而言,“房屋租售比”= “每平方米建筑面积的月租金”/ “每平方米建筑面积的房价”。但为了便于理解,我们可以把这个概念颠倒一下,转变为“房屋售租比”。

倒过来,“房屋售租比”=“每平方米建筑面积的房价”/“每平方米建筑面积的月租金”,其含义可以简单理解为:“在保持当前的房价和租金条件不变的情况下,完全收回投资需要多少个月”。

一般而言,按照国际经验,在一个房产运行情况良好的区域,应该可以在200-300个月内完全回收投资。如果少于200个月(17年)就能收回投资,说明这个地区有较高的投资价值;如果一个地区需要高于300个月(25年),比如1200个月(100年)才能回收投资,则说明该地区有潜在的房产泡沫风险。

200~300个月的安全区间,这是怎么算出来的呢?

简单举个例子吧,我准备投资一套房产,不想占用太多现金流,怎么办?付个首付,问银行贷款呗,能贷多少贷多少,大不了租金全部用来还按揭。好的没问题,这样的话,假如房价和租金均保持不变,租金也必须能够跑赢贷款利率才行。所以,一般国际上所认为的合理的租售比本质上是一个与贷款利率挂钩的指标。

以上海为例,现行贷款利率差不多算是6%吧(新政前后有高有低),那么按照这套租金跑赢贷款利率的逻辑,合理的售租比应该是多少呢?粗算一下,就是差不多200个月。

那么,上海有多少房子的售租比能达到200呢?

我们搜集了上海4月份的37000余套住房的租金数据和34000余套住房的房价数据,并按照小区(约12000个)进行了匹配,将每一个小区的租售比汇总到了每一平方公里的城市空间当中,并以200个月为分界值制作出下图:
&lt;img src=”https://pic1.zhimg.com/dea82966bdca9fc3e579ec43c303cb3c_b.jpg&#8221; data-rawwidth=”405″ data-rawheight=”405″ class=”content_image” width=”405″&gt;
是的,你没有看错,上图中的红点表示了售租比高于200个月的栅格空间,而白点则表示了售租比在200个月以下的栅格空间。也就是说,现阶段上海几乎没有什么所有地区的房价租金是能跑赢房贷的。

真是痛(hao)心(bu)疾(yi)首(wai)的结果啊。

不要紧,痛定思痛,再来看一下,上海的售租比到底是一个什么水平呢?我们将所有小区的租售比数值进行了排序,求出了其中位数值:

522个月。

什么概念?假设我25岁研究生一毕业,就立刻全款买了一套房,然后放出去收租,那么,在我约69岁时,这44年来陆陆续续所收的租金总数就可以达到了25岁时买房所付的钱啦。(而且还不考虑什么净现值折算之类的)。

好开心啊。才44年,竟然没有超过房屋的70年产权期呢。

高兴归高兴,我们由这些数据暂时可以得到以下两个结论:

1、在不考虑房产增值的情况下,上海绝大多数地方的房子的房租收入都跑不赢当下的商业贷款利率的。如果房价不持续上涨的话,即使以市场价出租,也是买一个月亏一个月,亏的程度不同而已;

2,如果仅靠租金收入的话,上海全市平均回收投资需要522个月,折算下来,只有约2.3%的收益。而这种格局的维持,必须有赖于购房者对上海的房价上升的持续预期。也就是说,在上海,投资房产绝不是利率收益,而是预期收益。

那么大家不禁要问了,参考国际惯例不是200到300个月么,那522个月的上海,仅靠预期收益,其房价是否有泡沫呢?

答案是:我不关心。

是的,即使租售比能够在某种程度上代表房产潜在的泡沫程度,这对于像我这样的一般购房者而言也毫无意义。为什么?很简单,买房是刚需啊。对于刚需购房者而言,他们需要做出的权衡并不是买不买房;而是怎么买一个泡沫小、风险低、收益预期高的房子。因此,我并不关心全上海的售租比包含多少绝对意义上的泡沫概念。我关心的问题是:

上海具体哪些房子的相对意义上的资产泡沫小一些?

我们先以户型分类,可以看到下图:
&lt;img src=”https://pic3.zhimg.com/32f59abba1b4259c668a0a9c67ddfd4a_b.jpg&#8221; data-rawwidth=”504″ data-rawheight=”360″ class=”origin_image zh-lightbox-thumb” width=”504″ data-original=”https://pic3.zhimg.com/32f59abba1b4259c668a0a9c67ddfd4a_r.jpg”&gt;
总体而言,售租比随着套型的增大而增大。其中一室户的租售比最低,中位数值为455个月,低于上海的全市值522个月,相对比较健康。而四室以上的豪宅则达到了590个月。

毫无疑问,这个结果令只买得起一室户的我十分欣喜(是的,这个分析毫无必要)。

那么,接下来的问题是,哪些区域售租比较低呢?

在看租售比的空间分布前,我们先研究一下房价和房租的空间分布。看下图。
&lt;img src=”https://pic2.zhimg.com/d8faa6fd13ed3242e6d89f462ec37e85_b.jpg&#8221; data-rawwidth=”841″ data-rawheight=”414″ class=”origin_image zh-lightbox-thumb” width=”841″ data-original=”https://pic2.zhimg.com/d8faa6fd13ed3242e6d89f462ec37e85_r.jpg”&gt;

左图是上海全市房价的空间分布,右图是上海房租的空间分布,可以发现,两者均表现为明显的向心性的圈层特征,而且分布的模式也基本一致。 左图是上海全市房价的空间分布,右图是上海房租的空间分布,可以发现,两者均表现为明显的向心性的圈层特征,而且分布的模式也基本一致。

那么租售比呢?再看下图。
&lt;img src=”https://pic1.zhimg.com/008a014936c064f026bcaf9a2f7ccb0c_b.jpg&#8221; data-rawwidth=”414″ data-rawheight=”414″ class=”content_image” width=”414″&gt; 毫无疑问,既然房价和租金在空间上表现为同质的圈层模式,那么作为两者相除的结果,售租比自然抹去了向心性的圈层分布特征,呈现出相对均质的扁平化分布特征,基本上看不出什么明显的特征。 毫无疑问,既然房价和租金在空间上表现为同质的圈层模式,那么作为两者相除的结果,售租比自然抹去了向心性的圈层分布特征,呈现出相对均质的扁平化分布特征,基本上看不出什么明显的特征。

那么,在这张混乱斑驳的图纸下面,到底隐藏着哪些因素影响了售租比呢?

理论上说,在一个完全理性的房产市场上,租金更多体现功能性,因此,售租比应该与每个地块轨道交通可达性、是否学区房、就业密度、以及高学历人口比例、人口密度等空间因素有关。
现实当中呢?是否如此?我们将这些因素分别代入到租售比模型当中,弄出一堆枯燥的统计学数字之后,结果出来了。请看下图:
&lt;img src=”https://pic2.zhimg.com/ca220dbbfe9d58e42f93482c7abaaecd_b.jpg&#8221; data-rawwidth=”163″ data-rawheight=”145″ class=”content_image” width=”163″&gt;
这张表是什么意思?简单一句话来解释吧:在上海全市层面,无论是学区房、轨交、就业密度还是人口,上述因素与售租比的关联均不显著

这是什么结论?难道这些经典理论中谈到的空间因素都不影响房屋租售比?这不科学啊!

别着急,我们的研究精神是百折不挠的。既然结果不显著,那么必然是某种变量没有被控制住嘛。让我们找到它。会不会是空间距离的关系呢?

于是,我们又将每一个空间栅格的售租比按照距市中心的距离排序,制作出了下面这张散点图:
&lt;img src=”https://pic4.zhimg.com/fafda18cd57fee62776927b491ceea67_b.jpg&#8221; data-rawwidth=”583″ data-rawheight=”363″ class=”origin_image zh-lightbox-thumb” width=”583″ data-original=”https://pic4.zhimg.com/fafda18cd57fee62776927b491ceea67_r.jpg”&gt;
在上图中,一个规律隐约地浮现出来:租售比的数值虽然并不随着空间距离增大或衰减,但其数值的离散程度却和空间距离有关。简单来说:

售租比越远离市中心越离散,越靠近市中心则越收敛。

其分界线大约是在20到30公里处,差不多就是上海中心城区和郊区的分野。是的,上海的租售比格局就是呈现这样一种“中心城区——郊区”分化特征的。

在获得这个认识后,我们再将轨交、学区、人口、就业等要素分为中心城区和郊区两个区间,分别代入到租售比模型中,便可以得到下表:
&lt;img src=”https://pic1.zhimg.com/6f719405b1b0b7e2c410622d6e8a16d8_b.jpg&#8221; data-rawwidth=”402″ data-rawheight=”217″ class=”content_image” width=”402″&gt;
注:* p < 0.05 ** p < 0.01

一句话解释一下吧:

中心城区的餐饮价格就业岗位密度产业结构这三个指标与房屋售租比有显著的相关性。总体来说,但在中心城区内部,人少岗位多、生产性服务业比例高、餐饮价格高的地方售租比会更低。但是这种关系在郊区并不成立。

这一结果说明了什么呢?

简单来说,中心城区的房产市场对空间因素的响应更加敏锐,而郊区则迟钝的多。这在某种程度上暗示着,在上海的郊区房产市场上,租金市场和住房市场可能是不重合的。

从理论上看,对于上海这个超大型城市,租金市场往往是本地化的,更注重房屋的功能性,因此就近的就业密度、产业结构、餐饮价格等等与之有紧密关联;但是,住房市场却是全市(国)的,包含了很强的投资性,而对于投资性购房者而言,他们看中的并非是租金收入而是预期收益,因而其选购房屋和就近的设施、就业密度、产业结构、餐饮价格等等的关联度就会较弱。

我们将这一理论假设回应到现实的结果中,就可以得出这样一个结论:

中心城区的房屋售租比的离散程度低,对空间因素的响应也更合理,说明该地区租房市场更成熟,房屋价格中包含的功能性价值也更强;而郊区的房产市场离散程度高,对空间因素无响应,说明该地区租房市场发育较弱,而房屋价格中包含的功能性也较弱,相应地更依赖投资性(或者叫预期性)价值。

在一栋房子所包含的价格中,究竟是功能性价值更安全,还是投资性(预期性)价值更安全呢?这个答案不言而喻了吧。

写到这里,我默默地在朋友圈里翻了一遍,发现在郊区买房的朋友其实不少。不止是我,大概每个漂在魔都的人都有那么几个在郊区置业的朋友、亲戚、或者同事吧。那些年,连夜排队、摇号购房的狂热景象刺激了很多买不起市中心的房子,但工资又没有房子涨的快的白领们。他们有些人选择在郊区买了房,也许目的并不是居住,而是在一轮风起云涌的大泡沫开始翻滚的时候,以正常人的理性选择来买一个泡沫而已。

那么现在呢?房价进入滞涨时代,他们是选择回归郊区的居住,还是卖掉郊区的房子然后努力置换到离工作岗位更近的市中心?我身边更多的案例往往是后者。

事实上,我并不知道上海的房地产市场是不是泡沫,这个问题需要更多的数据和更深入的探讨。我只知道,无论它是不是,我们这些漂在魔都的人也都会奋不顾身地去买一个泡沫。问题更多在于:

你是希望选择一个可能会涨得更大的泡沫,还选择一个破了之后不会溅到自己一身一脸的泡沫呢?

换个问题可能就会是,你要选择郊区,还是市区呢?

我想这是一个属于每一个人的自己的选择。

哦对了,很多北京的朋友总是向我咨询帝都的问题。说实在的,首都的问题高深莫测,一直无法摸到门径。但今天简单看了一眼,根据今年四月份的数据,帝都的全市售租比中位数是555个月,比上海还要多33个月。唉,上海总是赶不上北京啊,魔都加油吧,请把泡泡吹得更大一些。

Categories: life is fun

面对泡沫,该持有资产还是持有货币?

September 20, 2016 Leave a comment

面对泡沫,该持有资产还是持有货币?

2016-07-06 21财闻汇

前言

显然,因错失太多机会,中国经济已经步入到了一个骑虎难下的困难境地,繁华背后是风声鹤唳,摆在政府面前的三个最大问题就是:资产价格过高、人民币估值过高、债务负担过重!每一个问题爆发,都能引发系统性的危机!都是政府不愿意看到的!也是百姓不愿意面对的!政府会怎么做?我们该怎么办?

作者:米公子,来源:米筐投资(ID:mikuangtouzi) 授权21财闻汇发布

1、三大泡沫之资产泡沫

20多年以来,中国经济模式一直都是以政府主导投资驱动为引擎的经济模式,在以投资、出口、消费三架马车的模型中,如今能主要关注的只有投资了,谈投资,主要的也就只有房地产经济了,所以,当下在中国谈资产价格,主要就是指房价。

北京近10年的房价变化↓↓↓

深圳近10年的房价变化↓↓↓

合肥近10年房价变化↓↓↓

兰州近10年房价变化↓↓↓

郑州数据较全,我们来看近15年郑州房价变化↓↓↓

可以看出,在近几年,房价涨幅加快,这是中国资产价格的基本面。

来看均价在3万以上的区排行↓↓↓

动则均价5万以上的小区比比皆是,已经成了全球最贵的区域。

中国房价TOP10城市和全球典型城市房价对比↓↓↓

显然,如果你说中国的房价还不够高,我就无语了。对于中国房价的泡沫问题,已经根本不需要去争论,这已经是危害到了国计民生的问题,已经是全球公认的问题。、

如果政府要去除资产泡沫,会发生什么负能量的事情?

1、去资产泡沫,直接说就是要降房价

2、降房价,则触动的是地方政府、开发商、购房者、银行四方的核心利益

3、房价降20%,首付20%的购房者则可能抛弃已购房产

4、房价降,土地价格就要降,地方政府的贷款、融资平台、地方债务怎么办?

5、房价降,对于买涨不买跌的中国大众来说,房地产市场会立即降温,开发商欠银行的钱怎么办?

6、房价跌,势必会引发债务产业链上的大面积债务违约,购房者不还贷、开发商还不上钱、地方政府卖不了地还不了钱,最终,银行坏账大面积产生。

7、中国特色的维权开始,购房者堵售楼部要求退房,处理不好就是群体事件。

如果政府要去除资产泡沫,会发生什么正能量的事情?

1、楼市降温,钱去哪里?会不会倒逼入实体经济?

2、如果政府能给企业家信心,一个中长期的信心,在中长周期内是坚决的去除资产泡沫,扶持实体经济的话,大量资金会转向实体经济。

3、只有民营经济的心转向了实体经济、只有钱流向了实体经济,中国的经济才有未来和希望,才能彻底解决当下的大多数经济问题。

4、理论上,这是最好的处理危机的方式。当然,阵痛是必须要有的,而且很痛,要为以前的不理性付出代价,这也是日本模式。地产经济不死,实体经济必然难活,因为在现在的中国,地产就是金融,金融就是地产,这个现状不改变,金融就起不到为实体经济服务的作用!

如果继续保资产,让资产泡沫继续,会发生什么?

1、对中国实体经济是致命摧毁,中国仅有的一点实体经济也会被摧毁。

2、对民众信心的严重伤害,虽然房价高涨不跌,但百姓也不是傻子,自然知道不合理,全国恐慌情绪会蔓延。

3、中国经济结构性调整的周期继续后延,中国经济进一步下滑。

4、像董潘所说,未来北京房价80万/平,这也是有可能的。

2、三大泡沫之人民币泡沫

什么是M2、GDP、通货膨胀、CPI?

M0就是社会上的现金,

M1就是M0+企业的活期存款,

M2就是M1+全国的定期存款。

如果把国家比作一个超市,M2的增长也就是货币总量的增长,GDP的增长可以简单看作是整个超市内货物数量增长。货物的增长速度跟不上钱的增长速度,货少钱多,货就会涨价。货涨价的幅度就是CPI的涨幅。

M2是最能体现信贷情况、判断经济基本面的一个数据。

中国15年以来的M2变化↓↓↓

我们的M2数据在过去15年膨胀了10倍!

中国与美国M2变化↓↓↓

可以看出,美国M2数据在过去十多年是平稳上升,没有剧烈变化,但中国的M2自2008年开始加速上升,在2009年超过美国,并继续保持高昂的姿态在上升,截止2015年底,中国的M2数值已经是美国的1.813倍!

中国与美国M2同比增速比较↓↓↓

如图,我们的M2扩张数据在过去10多年间,一直远超美国!

中国与世界四大经济体的M2与GDP数据比较↓↓↓

可以看出,目前中国的货币可以出去买下整个美国和多半个欧盟,到2016年底,我们就可以买下整个美国和整个欧盟了!

中国目前的M2增速是13%左右,全球M2增速是7%左右,照此速度不变、人民币不贬值的话,不出10年,中国可以买下全世界!当然,这是理论上,你问问你自己,这可能吗?这背后的问题有多大?如果你是上帝,你会怎么想?怎么做?

所以,人民币贬值,已经是全球公认的事实,只是贬值速度、何时贬值的问题。

如果人民币直接大幅贬值,会发生什么负能量的事情?

1、国内的资金恐慌,争相跑出中国寻求安全的庇护所,中国外汇储备必将很快耗干,人民币贬值就变得不可遏制。

2、如果中国强行管制,违背的就是全球经济公约和一系列合作协议要求,人民币国际化的步伐直接停止,国际指责如何应对?

3、主权债务问题也会发生,主权评级也会下调,对中国在国际形象影响较大。

4、人民币贬值后,我们大量对外依存的进口商品(如大豆、石油、矿石、农产品、精密仪器、药品 等)将成本大大提升,势必加重国内人民负担,提高国内生活成本,造成输入性的通货膨胀!

5、人民币贬值,出国旅游、海外投资就降温了,影响的是企业或有钱人。

6、人民币贬值,外汇出逃时,通常政府都会加息来对冲,反过来,加息会加重危机中的国内经济,加重企业负担和债务负担,处理不当,也会引发连锁危机。

如果人民币直接大幅贬值,会发生什么正能量的事情?

1、人民币贬值影响最多的是有钱人,是老板,是外企,是外资,是热钱,这些人毕竟是少数。

2、基本上,老百姓不会闹事,因为,对于没有大多数没有存款的老百姓来说,只要房价不跌,抱着房子睡觉最安心,那怕人民币贬值80%呢,和百姓没啥关系。

3、对出口企业利好,可以改变近几年出口贸易一直不振的现状。

4、对国家和政府最好的是,基本上不会有群体事件发生。

如果保人民币,不贬值,会发生什么?

1、在全球公认的估值过高之下,大多数的钱会选择趁高位离开中国,要不出去购买资产,要不出去寻求安全。

2、在钱逐步离开中国之时,外汇耗干之时,人民币不想贬值也要贬值。

3、目前人民币民以每月400-500亿的速度外流,不出意外,2018年中,外汇储备就会耗干。都没外汇储备了,还说不贬值?打脸吗?

4、亲,也许你们也只有两年的时间,两年后,想换外汇,也没有了。

3、三大泡沫之债务泡沫

不容忽视的债务数据:

1、中国企业债务已经占到了GDP的1.9倍

2、地方政府债务已经占到了GDP的50%,达30多万亿

3、法兴银行在一份研报中称,中国银行业不良资产贷款率达15%左右

4、企业债务(包括国企)违约状况已经接连到来

如图,信贷在扩张,但增速在下降↓↓↓

20年以来,我们一直得以生存的投资式经济发展模式,突然行不通了!突然走不动了!信贷在扩张,经济却没有增长,这是很可怕的事情,从去年的股灾、今年的房灾、以及中间发生的螺纹钢事件,以及未来可能发生的农产品动荡事件,都与此有关,因为,M2在扩张,钱太多,却无处可去,就流向可炒作的资源,所到之处,无不造成重大动荡和伤害。

深层次影响的就是整个中国企业界的投资信心↓↓↓

我们看到,从2016年初开始,民间投资信心锐减,这是很可怕的事情。

这个情绪的蔓延,很可怕,最终导致的就是大面积的债务违约,就是经济危机。

一张100元的钞票,我相信你,这就是100块钱,我不再相信你时,这就是一张废纸。

债务泡沫根本上还是资产泡沫的延伸

1、过去10年以来的房价上涨,就是一直靠债务扩张来维持的,房价涨、地价涨、信贷扩张、债务扩大。

2、债务泡沫破裂会影响并刺破资产泡沫,资产泡沫破裂也会影响并刺破债务泡沫,两者是相互依存的骨肉兄弟。

3、而债务大面积违约,最终会导致,经济危机,会形成群体事件,这是国家和政府不愿意看到的。

4、为什么房价和汇率不可兼得?

如果想保房价?

就是让所有人都认为房价还要上涨,于是购房者继续买、开发商继续拿地,不管是因为恐慌还是因为期待,反正就是认为房价还要涨,怎么玩?

就只能靠继续信贷扩张、继续增加M2投放、给开发商和地方政府继续加杠杆、继续借新还旧维持房价上涨,钱越来越多,钱继续往房地产市场上涌,泡沫继续拱,大家争相购买,房价自然就降不下来,地价也是一路涨,资产价格一路升,如同你在2016年上半年看到的一样。

在此情况之下,国内实体经济肯定是继续下滑,消费也是不振的,国内的钱会越来越多,M2余额越来越大,远超世界平均水平,越来越多的人对未来充满恐慌,人民币预期估值肯定就会再被下调,加重人民币贬值压力。

如果想保汇率?

想保汇率,就要遏制资金外流,如何不让资金外流?就是让钱自己知道,出不出去差别不大,比如国内资产已经贬值了,钱出去还不如在中国呢,反正国内资产已经贬值这么低了,出去再回来也赚不到钱,还不如在国内呆着看那个企业顶不住资产贬值带来的压力要破产,抄底收购优质资产更重要,其实就是要增加钱呆在国内的预期,减小钱出去之后的预期。

钱不再外流了,汇率自然也就保住了。

所以,保房价、保汇率是两条不一样的经济思路,不可兼得,不可同行!政府只能选择一条路去走!

5、政府会怎么做?

政府会怎么做,也就是选择东京泡沫破裂模式还是莫斯科泡沫破裂模式的区别。

日本模式:刺破资产泡沫

日本房地产也是在1985-1990经历了疯狂的泡沫式增长,最终,日本选择的是去除资产泡沫的解决方式,日本房价下降50%,但日元和美元汇率变化不大,最终,也教育了整个日本,从此20多年,日本房价再也没有起来过,所有的企业都在乖乖的从事着以前的实体制造业。

当然,那次经济危机也给日本经济造成了重伤,房价下跌下的债务危机全面爆发,购房者跳楼、开发商破产、企业破产、银行破产。。。。那些在高位接盘的购房者不得不面对现实,用余生来偿还高额的房贷。

而,很多日本企业,又不得不出售很多的国外资产,回国救灾。

好的是,伤害了日本人民,也最终挽救了日本经济。

莫斯科模式:保资产、弃汇率

缘于乌克兰事件,2014年,世界原油价格开始暴跌,从年初的100美元跌至年底的53美元,以美国为首的西方国家集团对俄罗斯实施了一系列经济制裁,并且不断加码,严重干扰了俄罗斯的金融形势乃至实体经济的发展,不仅中断了俄罗斯在西方的融资通道,而且还促使俄罗斯国内资本(包括外来投资和境内资本)大量外逃,外汇需求猛增,卢布遭遇集中抛售。

2014年上半年1美元还可以兑换35卢布,到12月中旬的时候不但腰斩过半,还曾下探1美元兑换80卢布的历史低位,贬值幅度超过50%。2014年12月15日卢布曾一夜暴跌13%,创近16年来最大单日跌幅。

为应对外汇锐减,数次加息后,基准利率从5.5%提高到17%,提高幅度高达惊人的209%。如此频繁地调整基准利率,并且提高幅度如此之大,利率绝对值如此之高,均为同期世界各经济体所罕见,也是俄罗斯本国自1998年金融危机以来的第一次。

货币贬值通常与通货膨胀伴随而行。据俄官方公布的数据,俄罗斯2015年1月通胀率达到15%,创7年新高。2015年俄罗斯食品价格涨幅达14.3%,通胀率达12.9%,在全球主要经济体中居第三位。

卢布贬值》加息》国内严重通胀,这是俄罗斯经济危机的发展轨迹,而在莫斯科,以卢布计价的房价,没有跌。

中国会怎么选择?

1、国家稳定是最大的根本,所以,从理性上分析,因为80%以上的人除了房子就没有存款,也没有什么国际视野和全球视野,只要房子不贬值,搂着房价就不闹事,管你人民币在国际上贬不贬呢,关我毛事?而我也不买进口东西也不出国旅游,贬就贬呗,于我何干?而维护社会稳定是最大的原则,所以,会选择国内保资产不贬值,在国际上牺牲人民币,让人民币贬值。

2、这是我们的判断,但让人民币贬值牺牲的是金字塔上层精英阶会的利益,而目前的绝大多数权力又还在他们手上,他们会愿意选择牺牲自己利益吗?这是一个利益博弈的过程。

3、但,在大是大非面前,我想,大势不可违。中国要选择的,俄罗斯模式是大概率事件。

4、在保资产价格、弃汇率的大趋势面前,人民币国内贬值也是大概率事件,未来北京房价涨到15万/平以上也是大概率事件,未来郑州涨到1.5-2万/平以上也是大概率事件,我知道很多人不愿意听到这句话,也不愿意看到这样的情况,其实我也一样,但这就是现实,不得不面对的现实。

5、在弃外汇的政策前提之下,不会走目前这样温水煮青蛙的慢跌模式,这样会增加恐慌情绪、加速资金外流,让问题累积加重,政府可能会选择快速贬值、然后企稳的模式,比如:在某一天突然政策性贬值10-20%,然后通过媒体、政策集中释放信息,告诉大家:贬值到位了,都不用跑了,跑也没啥意思了,目前的中国更安全。

6、所以,在汇率快速断崖式贬值之前,看到的人请不要犹豫、要快速行动,能帮到一个是一个。如果你愿意相信并认同,记得转给你的朋友!目前美联储6月不打算加息,我们还有时间!

6、我们该怎么办?

如果,我们的预判准确,我们该怎么应对?建议如下:

1、在海外有账户的,可以把部分存款兑换成美元汇到海外,这是最佳的选择,只要到了海外就是自由了,然后想买房、买土地就随便了,但首先是要汇出去,而且要快。而且时间已经不多了,管制会越来越严格。以后待事态稳定,再汇回来也不晚。

2、在海外没有账户的,也可以考虑在国内兑换一部分美金,但也不建议买太多,因为只能是持有,不能增值,也不能作别的投资。换成现金放家里也不是个事儿,把美元存在银行也还会受到管制,不让你用你也没办法。

3、可以考虑在国内购买人民币黄金资产,比如人民币黄金ETF等,记得,一定是人民币黄金,当人民币贬值时,人民币黄金一定会涨,但现在人民币黄金已经在涨的路上了,最好的入场时机(2015年下半年)已经过去了,但为时还不算太晚。

4、不懂外汇、也不懂黄金的,建议在核心城市、成熟区域购买房子,不要去购买郊区房,但这一条只能算是中下策吧,虽然我们判断房价不会跌,核心城市房价还要继续涨,政府会在国内保资产,在国际上牺牲人民币,但毕竟政府也不是万能的,如果到时控制不好,国内恐慌时,也不好说,但整体上,这是相对安全靠谱的。比如2015年的股灾,虽然这事过去了,但当时过程中政府的束手无策、笨拙应对也是很明显的,而且,危机越大、问题越深,政府会越来越无策。

5、没有了。

适当的核心城市成熟区域房产持有+适当的人民币黄金持有+适当的家庭资产海外储备=是当下最佳的对冲选择!

考虑到国内高通涨的预期,在个人能力范围之内,能贷款就贷款,然后配置成上述资产。

记住要多元化配置,不要把资产都放一个蓝子里!

结语:

有人会说,把人民币和房价全圈在国内,那岂不是严重的通货膨胀吗?这不一样会造成社会动荡吗?当然,我知道的,只是,这些事又是靠后的事,问题是,当下的这两大难题,在资产泡沫和人民币泡沫面前,政府有得选择吗?

民众、企业都已经很恐慌了,只能二选一,刺破一个,稳定人心,没有别的选择。这是关键。

Categories: life is fun

为什么Uber宣布从Postgres切换到MySQL?


为什么Uber宣布从Postgres切换到MySQL?

2016-07-29 Evan Klitzke 高可用架构

导读:近期 Uber 宣布将数据库从 Postgres 迁移到 MySQL,在多个技术社区中引起了轩然大波,通过本文我们来详细了解 Uber 做出以上决策背后的原因。

介绍

Uber 的早期架构是由 Python 编写一个单体后端应用程序,使用 Postgres 作为数据持久化。后来 Uber 架构经历一系列显著改变,朝着微服务架构和新的数据平台发展。具体而言,在许多以前使用的 Postgres 的场景,现在更多的使用构建在 MySQL 之上的 schemaless 存储系统(小编:Uber的数据中间件)。在本文中,将探讨一些我们发现的 Postgres 的弊端,并解释我们切换 schemaless 和其他后端服务到 MySQL 数据库的原因。

Postgres 架构概述

我们遇到的大量 Postgres 限制如下:

  • 效率低下的写入架构
  • 低效的数据复制
  • 表损坏的问题
  • 糟糕的 MVCC 从库支持
  • 难以升级到新的版本

我们将在所有这些限制,首先通过分析 Postgres 如何组织在磁盘上的表和索引进行分析,特别是比较与 MySQL 使用 InnoDB 存储相同数据的实现方式。需要注意的是,我们在这里提出的分析主要是基于我们有些老的 Postgres 9.2 版本系列的经验。但据我们所知,本文中讨论的 PG 内部架构,并没有显著在新的 Postgres 版本中改变,就如在 9.2 版的磁盘数据设计,也没有比 Postgres 的 8.3 版(10 年前的版本)有什么显著变化。

磁盘数据格式

关系数据库必须执行一些关键任务:

  • 提供插入/更新/删除功能
  • 进行 schema 更改的能力
  • 实现多版本并发控制 (MVCC)机制,使不同的连接分别有他们各自的数据的事务视图

考虑如何将上述这些功能组合在一起工作,是数据库设计时的重要考虑部分。

Postgres 的核心设计之一是不变的(immutable)行数据。这些不变的行在 Postgres 中称为“tuple”。Tuple 在 Postgres 内部实现中由 CTID 来唯一标识 。一个 CTID 代表 tuple 在磁盘上的位置(即物理磁盘偏移)。多个 ctid 可以潜在描述一个单列(例如,当用于 MVCC 目的,或存在的行的多个版本时,行的旧版本尚未被 autovacuum 回收处理)。有组织的 tuple 的集合形成表。表本身具有的索引,通常被组织为 B 树数据结构,映射索引字段到 CTID 的负载。

通常,这些 ctids 对用户透明,但知道他们是如何工作,可以帮助您了解 Postgres 在磁盘上的数据结构。要查看某行当前 CTID,可以在查询的时候显式加上 “CTID”:

为了解释布局的细节,让我们考虑一个简单的用户表的例子。对于每个用户,我们有一个自动递增的用户 ID 的主键,还有用户的名字和姓氏,以及用户的出生年份。我们还定义了用户的全名复合二级索引(姓和名),并在用户的出生年份加上另一个二级索引。创建这样一个表 DDL 可能是这样的:

注意这个定义中的三个索引:主键索引加上两个二级索引。

对于本文中的例子,我们看下表的数据,它由一个选择有影响力的历史数学家开始:

如上所述,每行中隐含有独特的,不透明 CTID。因此,我们可以这样理解表的内部结构:

主键索引,它映射 ID 与 ctids,是这样定义的:

B 树被用在 id 字段上,B 树中的每个节点上保存了 CTID 值。注意,在这种情况下,在 B 树的字段的顺序,刚好与表中顺序相同,这是由于使用自动递增的 id 的缘故,但这并不一定需要是这种情况。

二级索引看起来相似;主要的区别是字段存储顺序不同,因为 B 树,必须按字典顺序组织。姓名索引(first,last)按字母表的顺序排列:


同样,birth_year 聚簇索引按升序排列,就像这样:

正如你所看到的,在这两种情况下,在各自的二级索引 CTID 字段本身并不是有序的,不象第一个自动递增的主键的情况。

假设我们需要更新此表中的记录。举例来说,假设要更新 al-Khwārizmī’ 的出生年份到 770 CE。正如前面提到的,行的 tuple 是不可变的。因此,要更新记录,需要添加一个新的  tuple。这种新的 tuple 有一个新的不透明 CTID,我们称之为 I。Postgres 需要能够从旧的 tuple D 处找到新的 I。在内部,Postgres 存储每个 tuple 中的版本字段,以及指向前一 tuple 的 ctid 指针(如果有)。因此,该表的新结构如下:

只要 al-Khwārizmī 的两个版本存在,索引则必须维护两行的记录。为简单起见,我们省略了主键索引并显示只有在这里的二级索引,它是这样的:


我们将旧版本标识成红色,将新版标识成绿色。在此之下,Postgres 使用另一个字段来保存该行版本,以确定哪一个 tuple 是最新的。这个新增的字段允许数据库确定事务看到的是那一个行的 tuple。


在 Postgres,主索引和二级索引都指向磁盘上的 tuple 偏移。当一个 tuple 的位置变化,各项索引都必须更新。

复制

当我们插入数据到表中,如果启用了流复制机制,Postgres 将会对数据进行复制,处于崩溃恢复的目的,数据库启用了预写日志 (WAL)并使用它来实现两阶段提交(2PC)。即使不启用复制的情况下,数据库也必须保留 WAL ,因为 WAL 提供了 ACID 的原子性(Atomicity)及持久性(Durability)能力。

我们可以通过如下场景来更好的理解 WAL,如果数据库遇到突然断电时意外崩溃,WAL 就提供了磁盘上表与索引更新变化的一个账本。当 Postgres 的守护程序再次启动后,就会对比账本上的记录与磁盘上的实际数据是否一致。如果帐本包含未在磁盘上的体现的数据,则可以利用 WAL 的记录来修正磁盘上的数据。

另外一方面,Postgres 也利用 WAL 将其在主从之间发送来实现流复制功能。每个从库复制数据与上述崩溃恢复的过程类似。流复制与实际崩溃恢复之间的唯一区别是,在恢复数据过程中是否能对外提供数据访问服务。

由于 WAL 实际上是为崩溃恢复目的而设计,它包含在物理磁盘的低级别更新的信息。WAL 记录的内容是在行 tuple 和它们的磁盘偏移量(即一行 ctids) 的实际磁盘上的代表级别。如果暂停一个 Postgres 主库,从库数据完全赶上后,在从库的实际磁盘上的内容完全匹配主库。因此,像工具 rsync 都可以恢复一个同步失败的从库。

Postgres 上述设计的大坑

Postgres 的上述设计给 Uber 在 PG 的使用上,导致了效率低下和其他很多问题。

1. 写放大(Write Amplification)

在 Postgres 设计的第一个问题是已知的写入放大 。

通常的写入放大是指一种问题数据写入,比如在 SSD 盘上,一个小逻辑更新(例如,写几个字节)转换到物理层后,成为一个更大的更昂贵的更新。

同样的问题也出现在 Postgres,在上面的例子,当我们做出的小逻辑更新,比如修改 al-Khwārizmī 的出生年份时,我们不得不执行至少四个物理的更新:

  1. 在表空间中写入新行的 tuple;
  2. 为新的 tuple 更新主键索引;
  3. 为新的 tuple 更新姓名索引 (first, last) ;
  4. 更新 birth_year 索引,为新的 tuple 添加一条记录;

事实上,这四步更新仅为了反映一个到主表的写操作;并且每个这些写入也同样需要在 WAL 得到体现,所以在磁盘上写入的总数目甚至比 4 步更大。

值得一提的是这里更新 2 和 3。当我们更新了 al-Khwārizmī 的出生年份,我们实际上并没有改变他的主键,我们也没有改变他的名字和姓氏。然而,这些索引仍必须与创建在数据库中的行记录了新的行的 tuple 的更新。对于具有大量二级索引的表,这些多余的步骤可能会导致巨大的低效。举例来说,如果我们有一个表上定义了十几个二级索引,更新一个字段,仅由一个单一的索引覆盖必须传播到所有的 12 项索引,以反映新行的 CTID。

2. 复制

因为复制发生在磁盘的变化上,因此写入放大问题自然会转化为复制层的放大。一个小的逻辑记录,如“更改出生年份为 CTID D 到 770”,WAL 会将上述描写的 4 步从网络上同步到从库,因此写入放大问题也等同一个复制放大问题,从而 Postgres 的复制数据流很快变得非常冗长,可能会占用大量的带宽。

在 Postgres 的复制发生一个数据中心内的情况下,复制带宽可能不是一个问题。现代网络设备和交换机可以处理大量的带宽,许多托管服务提供商提供免费或廉价的内部数据中心带宽。然而,当复制必须在不同数据中心之间发生的,问题都可以迅速升级

例如,Uber 原本使用的物理服务器在西海岸机房。为了灾难恢复的目的,我们在东海岸托管空间添加了一批服务器。在本设计中,我们西部数据中心作为主库,东海岸增加了一批服务器作为从库。

级联复制可以降低跨数据中心的带宽要求,只需要主库和一个从库之间同步一份数据所需的带宽和流量,即便在第二个数据中心配置了多个从库。然而,Postgres 的复制协议的详细程度,对于使用了大量二级索引的数据库,仍可能会导致数据的海量传输。采购跨国的带宽是昂贵的,即使有钱的土豪公司,也无法做到跨国的带宽和本地的带宽一样大。

这种带宽的问题也导致我们曾经在 WAL 归档方面出现过问题。除了发送所有从西海岸到东海岸的 WAL 更新,我们将所有的 WAL 记录归档到一个文件存储的 Web 云服务,这样当出现数据灾难情况时,可以从备份的 WAL 文件恢复。但是流量峰值时段,我们与存储网络服务的带宽根本无法跟上 WAL 写入的速度。

3. 数据损坏

在一次例行主数据库扩容的变更中,我们遇到了一个 Postgres 9.2 的 bug。从库的切换时间顺序执行不当,导致他们中的一些节点误传了一些 WAL 记录。因为这个 bug,应该被标记为无效的部分记录未标记成无效。

以下查询说明了这个 bug 如何影响我们的用户表:

SELECT * FROM users WHERE ID = 4;

此查询将返回两条记录:修改出生年份之前的老记录,再加上修改后的新记录。如果将 CTID 添加到 WHERE 列表中,我们将看到返回记录中存在不同的 CTID 记录,正如大家所预料的,返回了两个不同行的 tuple。

这个问题是有几个原因非常伤脑筋。首先,我们不能轻易找出这个问题影响的行数。从数据库返回的结果重复,导致应用程序逻辑在很多情况下会失败。我们最终使用防守编程语句来检测已知有这个问题表的情况。因为 bug 影响所有服务器,损坏的行在不同的服务器节点上可能是不同的,也就是说,在一个从库行 X 可能是坏的,Y 是好的,但对另一个从库,用行 X 可能是好的,Y 行可能是坏。事实上,我们并不确定数据损坏的从库节点数量,以及主库是否也存在数据损坏。

虽然我们知道,问题只是出现在每个数据库的少量几行,但我们还是非常担心,因为 Postgres 复制机制发生在物理层,任何小的错误格式有可能会导致彻底损坏我们的数据库索引。B 树的一个重要方面是,它们必须定期重新平衡 ,并且这些重新平衡操作可以完全改变树的结构作为子树被移到新的磁盘上的位置。如果错误数据被移动,这可能会导致树的大部分地区变得完全无效。

最后,我们追踪到了实际的 bug,并用它来确定新的 master 不存在任何损坏行。然后再把 master 的快照同步到所有从库上去,这是一个艰苦的体力活的过程(小编:看到美帝的 DBA 也这么苦逼心理终于平衡一点了),因为我们每次只能从在线的池子里面拿出有限几台来操作。

虽然我们遇到的这个 bug 仅影响 Postgres 9.2 的某些版本,而且目前已经修复了很久。但是,我们仍然发现这类令人担忧的 bug 可以再次发生。可能任意一个新的 Postgres 版本,它会带着这种致命类型的 bug,而且由于其复制的不合理的设计,这个问题一旦出现,就会立即蔓延到集群中所有复制链的数据库上。

4. 从库无 MVCC

Postgres 没有真正的从库 MVCC 支持。在从库任何时刻应用 WAL 更新,都会导致他们与主库物理结构完全一致。这样的设计也给 Uber 带来了一个问题。

为了支持 MVCC,Postgres 需要保留行的旧版本。如果流复制的从库正在执行一个事务,所有的更新操作将会在事务期间被阻塞。在这种情况下,Postgres 将会暂停 WAL 的线程,直到该事务结束。但如果该事务需要消耗相当长的时间,将会产生潜在的问题,Postgres 在这种情况下设定了超时:如果一个事务阻塞了 WAL 进程一段时间,Postgres 将会 kill 这个事务。

这样的设计意味着从库会定期的滞后于主库,而且也很容易写出代码,导致事务被 kill。这个问题可能不会很明显被发现。例如,假设一个开发人员有一个收据通过电子邮件发送给用户一些代码。这取决于它是如何写的,代码可能隐含有一个的保持打开,直到邮件发送完毕后,再关闭的一个数据库事务。虽然它总是不好的形式,让你的代码举行公开的数据库事务,同时执行无关的阻塞 I / O,但现实情况是,大多数工程师都不是数据库专家,可能并不总是理解这个问题,特别是使用掩盖了低级别的细节的 ORM 的事务。(小编:美帝程序员代码习惯跟咱们也很类似)

Postgres 的升级

因为复制记录在物理层面工作,这导致不能在不同的 Postgres GA 版本之间进行复制。运行的 Postgres 9.3 主数据库无法复制数据到 Postgres 9.2 的从库上,也无法在运行 9.2 的主数据库复制数据到 Postgres 9.3 的从库上。

我们按照以下这些步骤,从一个 Postgres 的 GA 版本升级到另一个:

  • 关闭主数据库。
  • 在主库上运行 pg_upgrade 命令,这是更新主库数据的命令 。在一个大的数据库上,这很容易需要几个小时的时间,执行期间不能够提供任何访问服务。
  • 再次启动主库。
  • 创建主库的新快照,这一步完全复制一份主库的所有数据,因此对于大型数据库,它也需要几个小时的时间。
  • 清除所有从库上的数据,将从主库导出的快照恢复到所有从库。
  • 把每个从库恢复到原先的复制层次结构。等待从库追上主库的最新的更新数据。

我们使用上述方法将 Postgres 9.1 成功升级到 Postgres 9.2。然而,这个过程花了太多时间,我们不能接受这个过程再来一次。到 Postgres 9.3 出来时,Uber 的增长导致我们的数据大幅增长,所以升级时间将会更加漫长。出于这个原因,我们的 Postgres 的实例一直运行 Postgres 9.2 到今天,尽管当前的 Postgres GA 版本是 9.5

如果你正在运行 Postgres 9.4 或更高版本,你可以使用类似 pglogical,它实现了 Postgres 的一个逻辑复制层。使用 pglogical,可以在不同的 Postgres 版本之间复制数据,这意味着升级比如从 9.4 到 9.5,不会产生显著的停机时间。但这个工具的能力依然存疑,因为它没有集成到 Postgres 主干,另外对于老版本的用户,pglogical 仍然不能支持。

MySQL 架构概述

为了更进一步解释的 Postgres 的局限性,我们了解为什么 MySQL 是 Uber 新存储工程 Schemaless 的底层存储 。在许多情况下,我们发现 MySQL 更有利于我们的使用场景。为了了解这些差异,我们考察了 MySQL 的架构,并与 Postgres 进行对比。我们特别分析 MySQL 和 InnoDB 存储引擎如何一同工作。Innodb 不仅在 Uber 大量使用,它也是世界上使用最广泛的 MySQL 存储引擎。

 

InnoDB 的磁盘数据结构

与 Postgres 一样,InnoDB 支持如 MVCC 和可变数据这样的高级特性。详细讨论 InnoDB 的磁盘数据格式超出了本文的范围;在这里,我们将重点放在从 Postgres 的主要区别上。

 

最重要的架构区别在于 Postgres 的索引记录直接映射到磁盘上的位置时,InnoDB 保持二级结构。而不是拿着一个指向磁盘上的行位置(如 CTID 在 Postgres),InnoDB 的第二个索引记录持有一个指向主键值。因此,在 MySQL 中的二级索引与相关联的主键索引键,是如下所示:

为了执行上的(first, last)索引查找,我们实际上需要做两查找。第一次查找表,找到记录的主键。一旦找到主键,则根据主键找到记录在磁盘上的位置。

这种设计意味着 InnoDB 对 Postgres 在做非主键查找时有小小的劣势,因为 MySQL 要做两次索引查找,但是 Postgres 只用做一次。然后因为数据是标准化的,行更新的时候只需要更新相应的索引记录。

而且 InnoDB 通常在相同的行更新数据,如果旧事务因为 MVCC 的 MySQL 从库而需要引用一行,老数据将进入一个特殊的区域,称为回滚段。

如果我们更新 al-Khwārizmī 的出生年份,我们看会发生什么。如果有足够的空间,数据库会直接更新 ID 为 4 的行(更新出生年份不需要额外的空间,因为年份是定长的 int)。出生年份这一列上的索引同时也会被更新。这一行的老版本被复制到回滚段。主键索引不需要更新,同样姓名索引也不需要更新。如果在这个表上有大量索引,数据库需要更新包含了 birth_year 的索引。因此,我们并不需要更新 signup_date,last_login_time 这些索引,而 Postgres 则必须全更新一遍。

这样的设计也使得 vocuum 和压缩效率更高。所有需要 vocuum 的数据都在回滚段内。相比之下,Postgres 的自动清理过程中必须做全表扫描,以确定删除的行。

 

MySQL 使用额外的间接层:二级索引记录指向主索引记录,而主索引本身包含在磁盘上的排的位置。如果一个行偏移的变化,只有主索引需要更新。

复制

MySQL 支持多个不同的复制模式:

  • 语句级别的复制:复制 SQL语句(例如,它会从字面上直译复制的语句,如:更新用户 SET birth_year = 770 WHERE ID = 4 )
  • 行级别的复制:复制所有变化的行记录
  • 混合复制:混合这两种模式

这些模式都各有利弊。基于语句的复制通常最为紧凑,但可能需要从库来支持昂贵的语句来更新少量数据。在另一方面,基于行的复制,如同 Postgres 的 WAL 复制,是更详细,但会导致对从库数据更可控,并且更新从库数据更高效。

在 MySQL 中,只有主索引有一个指向行的磁盘上的指针。这个对于复制来说很重要。MySQL 的复制流只需要包含有关逻辑更新行的信息。复制更新如“更改行的时间戳 x 从 T_ 1 至 T_ 2 ”,从库自动根据需要更新相关的索引。

相比之下,Postgres 的复制流包含物理变化,如“在磁盘偏移8382491,写字节XYZ。” 在 Postgres 里,每一次磁盘物理改变都需要被记录到 WAL 里。很小的逻辑变化(如更新时间戳)会引起许多磁盘上的改变:Postgres 必须插入新的 tuple,并更新所有索引指向新的 tuple。因此许多变化将被写入 WAL。这种设计的差异意味着 MySQL 复制二进制日志是显著比 PostgreSQL 的 WAL 流更紧凑。

复制如何工作也会影响从库的 MVCC。由于 MySQL 的复制流使用逻辑的更新,从库可以有真正的 MVCC 语义; 因此,读库查询不会阻塞复制流。相比之下,Postgres 的 WAL 流包含物理磁盘上的变化,使得 Postgres 的从库无法应用复制更新从而与查询相冲突,所以 PG 复制不能实现 MVCC。

MySQL 的复制架构意味着,bug 也许会导致表损坏,但不太可能导致灾难性的失败。复制发生在逻辑层,所以像一个重新平衡 B tree 这样的操作不会导致索引损坏。一个典型的 MySQL 复制问题是一个语句被跳过(或较少一点的情况,重复执行)的情况下。这可能会导致数据丢失或无效,但不会导致数据库出现灾难问题。

最后,MySQL 的复制架构使得它可以在 MySQL 不同版本之间进行复制。MySQL 只在复制格式改变的时候才增加版本号,这对 MySQL 来说很不常见。MySQL 的逻辑复制格式也意味着,在磁盘上的变化在存储引擎层不影响复制格式。做一个 MySQL 升级的典型方法是在一个时间来更新应用到一个从库,一旦你更新所有从库,你可以把它提为新的 master。这个操作几乎是 0 宕机的,这样也能保证 MySQL 能及时得到更新。

其他 MySQL 设计优势

到目前为止,我们集中于 Postgres 和 MySQL 在磁盘上的架构。MySQL 的架构导致性能比 Postgres 有显著优势。

缓冲池设计

首先,两个数据库缓冲池的工作方式不同。Postgres 用作缓存的内存比起内存的机器上的内存总数小很多。为了提高性能,Postgres 允许内核通过自动缓存最近访问的磁盘数据的页面缓存。举例来说,我们最大的 Postgres 的节点有 768G 可用内存,但只有大约 25G 的内存实际上是被 Postgres 的 RSS 内存使用,这让 700 多 GB 的可用内存留给 Linux 的页面缓存。

这种设计的问题是,相比访问 RSS 内存,操作系统的页面缓存访问数据实际上开销更大。从磁盘查找数据,Postgres 执行 lseek 和 read 系统调用来定位数据。这些系统调用的招致上下文切换,这比从主存储器访问数据更昂贵。事实上,Postgres 在这方面完全没有优化:Postgres 没有利用的 pread(2)系统调用,pread 会合并 seed + read 操作成一个单一的系统调用。

相比之下,InnoDB 存储引擎实现了自己的 LRUs 算法,它叫做 InnoDB 的缓冲池。这在逻辑上类似于 Linux 的页面缓存,但在用户空间实现的,因此也显著比 Postgres 设计复杂,InnoDB 缓冲池的设计有一些巨大的优势:

  • 使得它可以实现一个自定义的 LRU 设计。例如,它可以检测到病态的访问模式,并且阻止这种模式给缓冲池带来太大的破坏。
  • 它导致更少的上下文切换。通过 InnoDB 缓冲池访问的数据不需要任何用户/内核上下文切换。最坏的情况下的行为是一个的出现 TLB miss,但是可以通过使用 huag page 来搞定。

 

连接处理

MySQL 的实现是对每个连接生成一个线程,相对来说开销较低;每个线程拥有堆栈空间的一些内存开销,再加上堆上分配用于连接特定的缓冲区一些内存。对 MySQL 来说扩展到 10,000 左右的并发连接不是罕见的事情,实事上我们现在的 MySQL 接近这个连接数。

Postgres 使用的是每连接一个进程的设计。这很明显会比每连接每线程的设计开销更大。启动一个新的进程比一个新的线程会占用更多的内存。此外,线程之间进行通讯比进程之间 IPC 开销低很多。Postgres 9.2 使用系统V IPC为IPC原语,而不是使用线程模型中轻量级的 futexes,futex 的非竞争是常见的情况,比 System V IPC 速度更快,不需要进行上下文切换。

除了与 Postgres 的设计相关联的内存和 IPC 开销,即使有足够的可用内存可用,Postgres 对处理大连接数的支持依然非常差。我们已经碰到扩展 Postgres 几百个活动连接就碰到显著的问题的情况,在官方文档中也没有确切的说明原因,它强烈建议使用独立的连接池来保证大连接数。因此,使用 pgbouncer 做连接池基本可行。但是,在我们后端系统使用过程中发现有些 BUG,这会导致开启大量的原本不需要的活跃连接,这些 BUG 也已经造成好几次宕机。

结论

Postgres 在 Uber 初期运行的很好,但是 PG 很遗憾没能很好适应我们的数据增长。今天,我们有一些遗留的 Postgres 实例,但我们的数据库大部分已经迁移到 MySQL(通常使用我们的 Schemaless 中间层),在一些特殊的情况下,也使用 NoSQL 数据库如 Cassandra。我们对 MySQL 的使用非常满意,后续可能会在更多的博客文章中介绍其在 Uber 一些更先进的用途。

作者 Evan Klitzke 是 Uber 核心基础架构组资深软件工程师。他也是一个数据库爱好者,是 2012 年 9 月加入 Uber 的一名早鸟。

英文原文:

Why Uber Engineering Switched from Postgres to MySQL

Categories: life is fun

Flask + Gunicorn + Nginx 部署


最近很多朋友都在问我关于 Flask 部署的问题,说实在的我很乐意看到和回答这样的问题,至少证明了越来越多人开始用 Flask 了。

之前我曾发表过一篇在 Ubuntu 上用 uwsgi + nginx 的 Flask 部署方法,说实在的 uwsgi 是个大坑可能用在 Django 上还好吧,不过用在 Flask 上未必就如此。至少 , uwsgi 是个极为折腾人的东西。总之,我是一直认为复杂的东西未必不好,但一定是不好用的。

我自己也经过多番的纠结与尝试,也终于找到了一个 Flask 上靠谱的部署方案。我现在公司的微信后端平台也采用这种部署方案。如果有兴趣的朋友也不妨一看,或者给我提些更好的方案,毕竟知识只有共享了才知道是否有价值。

我在 Flask 官方文档中找到其中一个有意思的内容,这里是原谅链接:Standalone WSGI Containers ,其中并没有 uwsgi 的身影。悻然,但是找到了一个不用折腾的 Flask 部署方案了 —— Gunicorn。

Ubuntu 上的准备

假定你是在腾迅云或者阿里云购买了VPS,那么直接执行以下指令吧,其它的不多解释了,无非就是准备一下 python 环境。

$ sudo apt-get update
$ sudo apt-get install python-dev python-pip python-virtualenv

然后安装 nginx

$ sudo apt-get install nginx

/var/www 目录下建立一个 myflask 的文件夹(你的项目目录),然后用 chmod 改一下权限

$ sudo mkdir /var/www/myflask
$ sudo chmod 777 /var/www/myflask

注:当然你可以使用 nginx 的默认网站目录 /usr/share/nginx/html

然后用 scp 指令直接将本机上的 flask 项目传到服务器:

$ scp -r myflask root@www.mydomain.com:/var/www/myflask

域名就改成地址或者你的服务器正在使用的域名,我这里是用 root 用户进入的,你得按你的服务器的用户来修改。两大云的默认根用户是:

  • 腾迅 :ubuntu
  • 阿里 :root

Gunicorn

Gunicorn 绿色独角兽 是一个Python WSGI UNIX的HTTP服务器。这是一个pre-fork worker的模型,从Ruby的独角兽(Unicorn )项目移植。该Gunicorn服务器大致与各种Web框架兼容,只需非常简单的执行,轻量级的资源消耗,以及相当迅速。

我曾经Google 过 Gunicorn 与 uwsgi ,都说uwsgi 的性能要比 gunicorn 高,所以最终结果就杯具了。不过,现在回过头来看这只 “独角兽”还为时不晚吧。

安装 Gunicorn

Gunicorn 应该装在你的 virtualenv 环境下,关于 virtualenv 就不多说了,如果没用过那就赶快脑补吧。安装前记得激活 venv

(venv) $ pip install gunicorn

运行 Gunicorn

(venv) $ gunicorn -w 4 -b 127.0.0.1:8080 wsgi:application

That’s all! 它的安装就这么简单。不过这里得作一个解释。就是最后的那个参数 wsgi:application 这个是程序入口,我得写个小小的范例来说明一下:

新建一个 wsgi.py 的文件, 注意,这里和 Flask 项目中常用的 manage.py 引导脚本是没有半点毛关系的。(这是我笨,以前一直没分清被uwsgi搞糊涂了)

# wsgi.py

from flask import Flask

def create_app():
  # 这个工厂方法可以从你的原有的 `__init__.py` 或者其它地方引入。
  app = Flask(__name__)
  return app

application = create_app()

if __name__ == '__main__':
    application.run()

好了,这个 wsgi:application 参数就很好理解了, 分两部:wsgi 就是引导用的 python 文件名称(不包括后缀/模块名)application就是 Flask 实例的名称。这样 gunicorn 就会找到具体要 host 哪一个 flask 实例了。

从这里开始就可以体现 gunicorn 的好了,我们根本不用配什么配置文件的,一个指令就可以将它起动。

Nginx 的配置

关于 Nginx 我也就不详细讲了,我们就直奔主题,杀入 Nginx 的默认配置文件

sudo nano /etc/nginx/site-avalidable/default

暴力修改成为以下的内容

建议先备份一下 default 文件
sudo cp /etc/nginx/site-avalidable/default /etc/nginx/site-avalidable/default.bak

server {
    listen 80;
    server_name example.org; # 这是HOST机器的外部域名,用地址也行

    location / {
        proxy_pass http://127.0.0.1:8080; # 这里是指向 gunicorn host 的服务地址
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }

  }

记得完成 nginx 需要重新起动 nginx 服务喔!

sudo service nginx restart

将 Gunicorn 作为服务运行

这就是最后一步了,我们在此将采用 UpStart 配置Flask程序作为服务程序在Linux起动时运行。首先建立起动配置文件:

sudo nano /etc/init/myflask.conf

然后加入如下配置

description "The myflask service"

start on runlevel [2345]
stop on runlevel [!2345]


respawn
setuid root
setgid www-data

env PATH= /var/www/myflask/venv/bin
chdir /var/www/myflask/

exec gunicorn -w 4 -b 127.0.0.1:8080 wsgi:application

OK 大功告成,启动 myflask 服务

sudo service myflask start

这里有一点必须补充的,请留意在 myflask.conf 中的

env PATH= /var/www/myflask/venv/bin
chdir /var/www/myflask/

这里所指向的地址就是你的项目路径和 virtualenv 的路径

小结

这个部署过程感觉比我之前所介绍的 uwsgi 那种简单很多吧。这里给一点小 Tips 如果你用 Fabric 来完成这个部署过程的话那么就是纯自动化部署了喔,值得尝试的。

Categories: life is fun