야드 관리와 툴

3월이 되면 잠자던 나무와 꽃들이 기지개를 피고 일어날 준비를 시작한다. 이때쯤 되면 겨우내 눈, 비와 바람에 날린 꺾인 나뭇잎과 가지들을 치워야 하고, 노랗게 죽은 잔디들을 솎아내 버려야 한다.

올해는 큰맘먹고 아마존에서 Dethatcher를 구입했는데 작년 초부터 말썽인 이끼들이 너무 번져 잔디들이 죽어가고 있기 때문이었다. 이 Dethatcher는 Rake를 대신하는 기계인데, 죽은 잔디, 이끼, 낙옆, 잔가지들을 솎아내주면서 땅을 긁어서 부드럽게 만들어준다. 이렇게 땅을 부드럽게 만들게 되면 잔디 씨와 비료를 뿌리기에 좋다.

이른 봄에 해야할 일들을 정리해봤다.

  1. 마당에 있는 큰 나뭇가지들을 치운다.
  2. Dethatcher로 죽은 잔디 등을 솎아낸다.
  3. Rake로 죽은 잔디 등을 긁어 모아 치운다.
  4. Leaf Blower로 야드를 깨끗이 한다.
  5. 잔디 씨(Over seeding)를 뿌린다.
  6. 비료(Fertilizer)를 뿌린다.
  7. 물을 준다.

잔디 씨를 뿌리면 2주동안 매일 땅을 촉촉하게 할 정도의 물을 줘야 하고, 비료는 1년에 2-3번 정도 주면 되는 데, 이른 봄에 한번 여름에 한번 가을에 한 번 주는 것이 좋다. 이번에 코스트코에서 구입한 잔디 씨는 비료와 같이 들어있는 제품이어서 #5 와 #6을 한방에 해결하였는데 처음써본 제품이라 만족도는 몇 주 지나봐야 알 것 같다.

필요에 의해 Lime도 뿌려줘야 하는데 이는 땅의 Ph를 맞춰주는 역할을 한다. 땅은 갈수록 산성이 되어가는데, 이렇게 산성이 되면 이끼가 쉽게 모습을 드러내기 때문에 알칼리성인 Lime을 뿌려주어 Ph를 맞추는 것이 좋다. 라임을 뿌리는 시기는 늦가을이 가장 좋고 이른 봄에 뿌려도 무방하다. 소나무가 있는 곳은 소나무잎때문에 땅의 산성도가 다른곳에 비해 높다. 잔디가 잘 자라는 Ph의 Level은 아주 약한 산성인 6.5가 가장 좋은데 Ph를 잘 맞추면 풍성한 잔디를 기를 수 있게 된다.

처음 야드 관리를 하는것이라면 필요한 툴들이 많이 있을텐데, 경제적으로 풍요롭지 않다면 꼭 필요한 툴 부터 구매해 나가는 것이 좋다. 나 또한 아래 툴 들을 모으는데 2-3년이 되었다. 야드 관리 업체를 1년 사용하는 비용은 대략 2-3천불 되고, 아래 툴 들을 구매하는 비용과 비슷하기 때문에 일년 해보면 본전 뽑는것이다.

해마다 필요한 물품들

  1. Grass Seed
    마당 넓이에 따라 크기 맞게 구매. 참고로 40lbs 짜리가 가성비 좋음.
    이른 봄이나 가을에 뿌리는 것이 좋음.
    https://www.homedepot.com/p/Scotts-40-lb-Turf-Builder-Grass-Seed-Sun-and-Shade-Mix-18334/203760900
  2. Patch용 Grass Seed (필요시에만 구매)
    4/17/2019 까지 세일 하는 가성비 최고의 패치용 잔디 씨 + 퍼틸라이저 + Soil
    이른 봄이나 가을에 뿌리는 것이 좋음.
    https://www.lowes.com/pd/Scotts-20-lb-Sun-And-Shade-Lawn-Repair-Mix/1000042425
  3. Fatilizer
    1년에 3번 뿌려주는 것이 가장 좋은데 2번 정도만 해도 괜찮은 듯 함. 종류가 너무 많고 잡초방지제와 같이 들어있는 제품들도 많이 있는데 내가 선호하는 제품은 유기농 비료이다.
    이른 봄에 한번, 여름에 한번, 가을에 한번 뿌리면 됨.
    https://www.homedepot.com/p/Milorganite-36-lb-Slow-Release-Nitrogen-Fertilizer-100048741/100618523
  4. Lime
    땅의 Ph를 맞춰주는 건데 땅이 점점 산성이 되면 한번씩 뿌려주면 됨. 대충 2-3년에 한번 뿌리면 될 듯.
    땅이 산성이 되면 이끼가 끼기 시작하는데 이끼의 생존력이 강해서 몇 달 안에 이끼밭이 됨. 이끼가 보이면 땅이 산성이 됐다는 의미니까 라임은 꼭 뿌리길.
    이른 봄이나 가을에 뿌림.
    https://www.homedepot.com/p/Oldcastle-Green-n-Grow-30-lb-Specially-Formulated-Limestone-54055018/100658972
  5. 이끼제거제(필요시에만 구매)
    위에도 말했듯이 땅이 산성이 된 경우 이끼가 보이기 시작하는데 산불과 이끼는 초기에 진압(?)해야 한다.
    https://www.homedepot.com/p/Scotts-5-000-sq-ft-Moss-Control-Granules-31015/204758942
  6. 목장갑
    코스트코에서 10쌍들이 한 묶음 구매.

필요한 툴들

  1. Rakes
    https://www.homedepot.com/p/Razor-Back-24-Tine-Steel-Rake-2915200/204476216
  2. Spreader
    https://www.homedepot.com/p/Scotts-Turf-Builder-Mini-Broadcast-Spreader-76121/100664982
  3. Lawn Mower
    Recommended: https://www.homedepot.com/p/EGO-21-in-56-Volt-Lithium-ion-Cordless-Walk-Behind-Self-Propelled-Mower-Kit-7-5-Ah-Battery-Charger-Included-LM2102SP/206515944
    https://www.homedepot.com/p/EGO-21-in-56-Volt-Lithium-ion-Cordless-Battery-Walk-Behind-Push-Mower-5-0-Ah-Battery-Charger-Included-LM2101/206515766
  4. String Trimmer
    https://www.homedepot.com/s/ego%2520string%2520trimmer?NCNI-5
    Recommended: https://www.homedepot.com/p/EGO-15-in-56-Volt-Lithium-ion-Electric-Cordless-String-Trimmer-w-Rapid-Reload-Head-with-2-5Ah-Battery-and-Charger-Included-ST1502SF/300638783
  5. Leaf Blower
    무조건 백팩용 가스 추천. 베터리는 파워가 약하고 충전 주기가 짧아서 사용하기 힘듦. 베터리용의 장점은 딱 하나. 가벼워서 포터블해 드라이브웨이 정도의 단시간에 쉽게 블로잉 할 수 있는곳에 적합.
    가장 좋은건 EGO 베터리용 블로어 하나랑 ECHO 가스 백팩 블로어 하나씩 있음 최고!!
    https://www.homedepot.com/s/echo%2520blower?NCNI-5
    Recommended: https://www.homedepot.com/p/ECHO-215-MPH-510-CFM-58-2cc-Gas-2-Stroke-Cycle-Backpack-Leaf-Blower-with-Tube-Throttle-PB-580T/205894359
  6. Loppers
    아래 Hedge Trimmer는 잔 가지를 예쁘게 정리할 때 사용하지만 Loppers는 조금 굵은 가지를 자르는데 사용된다.
    https://www.homedepot.com/p/Fiskars-1-1-2-in-Cutting-Capacity-Steel-Blade-Loppers-376101/308132145
  7. Hedge Shears
    a.k.a. 전지가위. 아래 Hedge Trimmer나 전기가위 둘 중 하나는 기본.
    https://www.homedepot.com/p/Fiskars-9-in-Power-Lever-Steel-Blade-Telescoping-Steel-Handle-Hedge-Shears-391690/205617374

있으면 좋은 툴들

  1. Dethatcher
    죽은 잔디 솎아주고 땅을 갉아줘서 스프링 클린업에 좋은 툴이고, 이걸로 정리하고 난 다음 씨 뿌리면 됨.
    https://www.amazon.com/gp/product/B0030BG1HM/ref=ppx_yo_dt_b_asin_title_o07_s00?ie=UTF8&psc=1
  2. Hedge Trimmer
    가지치기(전지) 용 톱인데 싼 제품들을 써도 무난하다. 탱탱한 팔 근육을 원한다면 스킵.
    https://www.homedepot.com/p/BLACK-DECKER-24-in-3-3-Amp-Corded-Electric-Hedge-Hog-Trimmer-with-Rotating-Handle-HH2455/202590054
  3. Sprayer
    주로 페이브나 원하지 않는 곳에 난 잡초들을 제거할때 제초제를 뿌리는데 제초제에 전동형 스프레이어가 달린 제품을 구입하는 경우엔 필요가 없지만 그렇지 않은 경우에는 필요한 제품.
    https://www.homedepot.com/p/Scotts-1-Gal-Multi-Use-Sprayer-190498/300259157

여름과 가을에 해야할 일들도 정리를 해서 업뎃을 할 예정이다.

Nginx stops by itself once in a while.

Nginx stops by itself occasionally and didn’t know the reason for it. When the server got down at 3rd time, I decided to dig into the problem.

I’ve looked up syslog first.

tail -5000f /var/log/syslog

Found the following messages.

May 12 00:51:39 ip-172-31-12-44 systemd[1]: Starting Certbot...
May 12 00:51:40 ip-172-31-12-44 systemd[1]: Stopping A high performance web server and a reverse proxy server...
May 12 00:51:40 ip-172-31-12-44 systemd[1]: Stopped A high performance web server and a reverse proxy server.
May 12 00:51:46 ip-172-31-12-44 certbot[15472]: nginx: [error] open() "/run/nginx.pid" failed (2: No such file or directory)
May 12 00:51:47 ip-172-31-12-44 systemd[1]: Starting A high performance web server and a reverse proxy server...
May 12 00:51:47 ip-172-31-12-44 nginx[15559]: nginx: [emerg] bind() to 0.0.0.0:80 failed (98: Address already in use)
May 12 00:51:47 ip-172-31-12-44 nginx[15559]: nginx: [emerg] bind() to [::]:80 failed (98: Address already in use)
May 12 00:51:47 ip-172-31-12-44 nginx[15559]: nginx: [emerg] bind() to 0.0.0.0:443 failed (98: Address already in use)
May 12 00:51:47 ip-172-31-12-44 nginx[15559]: nginx: [emerg] bind() to 0.0.0.0:80 failed (98: Address already in use)
May 12 00:51:47 ip-172-31-12-44 nginx[15559]: nginx: [emerg] bind() to [::]:80 failed (98: Address already in use)
May 12 00:51:47 ip-172-31-12-44 nginx[15559]: nginx: [emerg] bind() to 0.0.0.0:443 failed (98: Address already in use)
May 12 00:51:48 ip-172-31-12-44 nginx[15559]: nginx: [emerg] bind() to 0.0.0.0:80 failed (98: Address already in use)
May 12 00:51:48 ip-172-31-12-44 nginx[15559]: nginx: [emerg] bind() to [::]:80 failed (98: Address already in use)
May 12 00:51:48 ip-172-31-12-44 nginx[15559]: nginx: [emerg] bind() to 0.0.0.0:443 failed (98: Address already in use)
May 12 00:51:48 ip-172-31-12-44 nginx[15559]: nginx: [emerg] bind() to 0.0.0.0:80 failed (98: Address already in use)
May 12 00:51:48 ip-172-31-12-44 nginx[15559]: nginx: [emerg] bind() to [::]:80 failed (98: Address already in use)
May 12 00:51:48 ip-172-31-12-44 nginx[15559]: nginx: [emerg] bind() to 0.0.0.0:443 failed (98: Address already in use)
May 12 00:51:49 ip-172-31-12-44 nginx[15559]: nginx: [emerg] bind() to 0.0.0.0:80 failed (98: Address already in use)
May 12 00:51:49 ip-172-31-12-44 nginx[15559]: nginx: [emerg] bind() to [::]:80 failed (98: Address already in use)
May 12 00:51:49 ip-172-31-12-44 nginx[15559]: nginx: [emerg] bind() to 0.0.0.0:443 failed (98: Address already in use)
May 12 00:51:49 ip-172-31-12-44 nginx[15559]: nginx: [emerg] still could not bind()
May 12 00:51:49 ip-172-31-12-44 systemd[1]: nginx.service: Control process exited, code=exited status=1
May 12 00:51:49 ip-172-31-12-44 systemd[1]: Failed to start A high performance web server and a reverse proxy server.
May 12 00:51:49 ip-172-31-12-44 systemd[1]: nginx.service: Unit entered failed state.
May 12 00:51:49 ip-172-31-12-44 systemd[1]: nginx.service: Failed with result 'exit-code'.
May 12 00:51:49 ip-172-31-12-44 certbot[15472]: Hook command "service nginx start" returned error code 1
May 12 00:51:49 ip-172-31-12-44 certbot[15472]: Error output from service:
May 12 00:51:49 ip-172-31-12-44 certbot[15472]: Job for nginx.service failed because the control process exited with error code. See "systemctl status nginx.service" and "journalcte>-xe" for details.

I remember that I’ve set up a nginx server with certbot and noticed that it’s complaining about restarting nginx during certification renewal process.

Thus, I opened up the certification configuration file as below.

sudo vi /etc/letsencrypt/renewal/mydomain.com.conf

I’ve changed the config file as below.

  1. Update authenticator from standalone to nginx.
  2. Update installer from nginx to none
  3. Comment out pre_hook.
  4. Update post_hook with service nginx restart.
# Options used in the renewal process
[renewalparams]
authenticator = nginx
installer = none
account = IDENTIFIER
#pre_hook = service nginx stop
post_hook = service nginx restart

To test the update, run the following command in order to simulate the renewal.

sudo certbot renew --dry-run

If it results without issues or errors, it’s good to go. 🙂

virtualenvwrapper configuration

There are files for hooks and configuration is very straight forward.
I’d like to create a new folder named src for storing all the code base. Then, change directory to the new folder. Let’s see what I can do.

There is postrmvirtualenv file under $WORKON_HOME. In my case, it is ~/venv.
Let’s open up the file in vi editor and add some hooks.

$ vi ~/venv/postrmvirtualenv

# Add following to the file and save.
path=$VIRTUAL_ENV/src
mkdir $path
cd $path

I’ve created a variable for the path of the source folder I mentioned. Then, create it and change directory to it.

Now, mkvirtualenv command lets you to create a new virtual env box and create a new folder called src under the project just created and change directory to the folder.

Let’s make some changes for workon command same as above.

I’d like to change directory to the src folder under the project once the project has been activated. Also, I’d like to change directory to the virtualenv root folder ~/venv once the project has been deactivated.

Open up postactivate and add below for activate behavior.

cd $VIRTUAL_ENV/src

Open up postactivate and add below for activate behavior.

cd $WORKON_HOME

That’s it and enjoy.

git setup with multiple keys(accounts)

I had 2 accounts in gitlab.com and I couldn’t use one rsa key to access both accounts. It seems like gitlab’s restriction.

So, I needed to create another key for the 2nd account. Let’s try.

  1. Generate a new key
    $ ssh-keygen -t rsa -b 4096 -C "your_email@example.com"
    Generating public/private rsa key pair.
    Enter file in which to save the key (/Users/myid/.ssh/id_rsa): /Users/myid/.ssh/new_rsa
    Enter passphrase (empty for no passphrase):
    Enter same passphrase again:
    Your identification has been saved in /Users/myid/.ssh/new_rsa.
    Your public key has been saved in /Users/myid/.ssh/new_rsa.pub.
    
  2. Update ssh config file
    $ vi ~/.ssh/config
    Host gitlab-com-new
      HostName gitlab.com
      User git
      IdentityFile ~/.ssh/new_rsa
    
  3. Add the new key created to ssh agent
    $ ssh-add ~/.ssh/new_rsa
  4. Add or update the git repository path
    $ git remote add origin git@gitlab-com-new:new_id/new_repository.git
    # or if the repository already exists
    $ git remote set-url origin git@gitlab-com-new:new_id/new_repository.git
  5. Enjoy 😛

Getting the last sub entries per entry

Now, I am trying to get the sizes of the last created versions of each contents.

Here’s the simple model diagram.

content_version_diagram

It looks just like a blog post and its comments. Here’s sample data.

Content model

id name
1 My Content 1
2 My Content 2

Version model

id parent_id size
1 1 100
2 1 200
3 1 300
4 2 400
5 2 500

The records with grayed background would be the result set as following table.

Expected result set (Versions only)

id parent_id size
3 1 300
5 2 500

 

My first thought was using group by with Max() function which requires a join or sub query. The 2nd thought was using distinct, but I wasn’t sure that grabbing the last ones only per parent. Anyway, here are the django orm and the sql queryset generated.
I tested with django-debug-toolbar’s debugsqlshell command which shows sql query strings per command with execution times. Below, I discard all execution times from the result because the result may vary based on the performance of the machine tested with.


$ ./manage.py debugsqlshell

>>> Version.objects.distinct('parent').order_by('parent_id', '-created_at')
SELECT DISTINCT ON ("content_version"."parent_id") "content_version"."id",
                   "content_version"."parent_id",
                   ...
FROM "content_version"
ORDER BY "content_version"."parent_id" ASC,
         "content_version"."created_at" DESC

Now I need to get size of each versions.

>>> Version.objects.filter(parent__owner=owner).distinct('parent').order_by('parent_id', '-created_at')
SELECT DISTINCT ON ("content_version"."parent_id") "content_version"."id",
                   "content_version"."parent_id",
                   ...
FROM "content_version"
INNER JOIN "content_content" ON ("content_version"."parent_id" = "content_content"."id")
WHERE ("content_version"."is_archived" = false
       AND "content_content"."is_archived" = false
       AND "content_content"."owner_id" = '65ed9958-788e-11e6-9b70-a820663cfc41'::uuid)
ORDER BY "content_version"."parent_id" ASC,
         "content_version"."created_at" DESC

Now I need to add a filter to getting none-archived records and owned by a user.

>>> Version.objects.filter(parent__owner=owner).distinct('parent').order_by('parent_id', '-created_at').values_list("size", flat=True)
SELECT DISTINCT ON ("content_version"."parent_id") "content_version"."size"
FROM "content_version"
INNER JOIN "content_content" ON ("content_version"."parent_id" = "content_content"."id")
WHERE ("content_version"."is_archived" = false
       AND "content_content"."is_archived" = false
       AND "content_content"."owner_id" = '65ed9958-788e-11e6-9b70-a820663cfc41'::uuid)
ORDER BY "content_version"."parent_id" ASC,
         "content_version"."created_at" DESC

So far so good. 🙂
Oh, Now I want to get the total size of all the last versions.
I tried to add aggregation to get Sum() of size, and the django's orm results broken queries. If anyone knows how to get the result with django's orm, please let me know. I may buy you a cup of coffee or beer sometime.

Anyway, so I decided to use raw sql for the result and I didn't want to use python's sum() function because the number of records was arbitrary and may get lots of records that would make the application slow down.

Here's the last code snippet I used.


def get_storage_bytes_used(owner):
    """
    Returns total bytes used for user.
    The calculation is Sum of latest versions of each `Content` records.

    Args:
        owner (User): An owner of Content object.

    Return:
        bytes_used (int): Total bytes used for a given user.
    """
    query = '''SELECT sum(size) from (
        SELECT DISTINCT ON (v."parent_id") v."size"
        FROM "content_version" AS v
        INNER JOIN "content_content" AS c ON (v."parent_id" = c."id")
        WHERE (v."is_archived" = false
            AND c."is_archived" = false
            AND c."owner_id" = '%(owner_id)s'::uuid)
        ORDER BY v."parent_id" ASC, v."updated_at" DESC) as t'''
    context = {'owner_id': owner.id}

    with connection.cursor() as c:
        c.execute(query % context)
        bytes_used = c.fetchone()

    return bytes_used[0] or 0

Happy coding.