Pages

Saturday, March 20, 2021

Qt의 오픈소스 버전 LTS 제공 중지는 Qt의 사용자 기반을 줄일 것인가?

작년(2020년)이었죠. Qt가 더이상 오픈소스 버전에 LTS를 제공하지 않기로 선언했습니다.
https://www.qt.io/blog/qt-offering-changes-2020

그리고 올해(2021년) 3월 4일, 상용으로만 제공되는 첫번째 LTS인 Qt 5.15.3이 발표되었습니다.
https://www.qt.io/blog/commercial-lts-qt-5.15.3-released

들어가 보시면 아시겠지만, 댓글창은 오픈소스 사용자들의 분노(?)의 도가니탕입니다. Qt 사용 인구가 줄어들 것이라던가, 관짝에 첫 못질을 시작했다던가 하는 등등의 이야기들로 말 그대로 불타오르고 있습니다. 뭐 저 곳 블로그는 조용할만하면 어떻게든 꼭 한번씩 불타오르는 터라 그냥 그러려니 하고 있습니다만......

그런데 냉정하게 생각해봅시다. LTS를 상용 라이선스 고객에게만 제공한다는 저 정책 때문에 Qt의 사용자 수가 줄어들까요? 솔직히 말해서 전 아니라고 봅니다. 당장 이 질문 하나만 던져보면 확실해지죠.

"그래, Qt를 안 쓴다고 치자. 그럼 뭐 쓸래?"

최소한 제가 알기로는, 여기에 1:1로 매칭되는 대안을 제공할 수 있는 라이브러리같은 것은 없습니다. 일부 기능을 대체할 수는 있겠지만, Qt 고유의 기능들은 대체가 불가능할 뿐더러(혹시 Qt Quick을 Electron으로 완전히 대체할 수 있다고 주장하시는 분들, Electron이 처묵처묵하는 저 엄청난 양의 RAM은 어쩌실 생각이신가요?), 다른 생태계를 끌어들이는 순간 그 프로그램은 유지보수의 난이도가 기하급수적으로 상승하게 될 겁니다. 게다가 그 '대체재'라는 것들 중 Qt가 제공하는 상용 수준의 문서와 일관성있게 잘 다듬어진 API를 쫓아올만한 것이 뭐가 있을까요? wxWidgets? 전 wxWidgets에서 Qt로 마이그레이션한 사례는 봤어도 그 반대사례는 보지 못했습니다.

당장 새로운 언어가 나오면 GUI를 위해 가장 먼저 바인딩되는 C++ 라이브러리 중 하나가 Qt라는 것만 봐도 Qt가 개발자들 사이에서 가지는 위상이 어떤지가 보이죠.

물론 오픈소스 커뮤니티의 분노(?)가 이해가 가지 않는건 아닙니다. 난 버그 발견하고, 보고하고, 심지어는 패치까지 무료로 주는데 그걸 돈주고 파는 사람에게만 모아주겠다는건 어떻게 보면 놀부 심보처럼 보일 수 있을겁니다. 하지만 뭐...... 그 대신에 우린 높은 수준의 완성도를 가진 라이브러리를 무료로 사용하고 있잖아요. 게다가 저 LTS라는 것, 2011년 이전에는 아예 존재하지도 않기도 했고...... (여담이지만, 오픈소스 프로젝트 중 LTS를 제공하는 프로젝트와 그렇지 않은 프로젝트를 구분하면 전자는 거의 손가락으로 꼽을 수준 정도밖에 안 될 겁니다)

뭐, LTS를 오픈소스에 제공하지 않는다는게 잘 하는 짓(?)이라는 이야기는 아닙니다. 해석하기에 따라서는 독점 시장의 폐해라고도 할 수 있지요. 단지 지금으로서는 적절한 대체재가 없다는게 문제입니다. 실제로 이쪽 시장이 생각보다 좁아서 Qt급의 회사가 두 개 이상 존재할만한 크기일 것 같지도 않고요. 인정할 것은 해야죠. 오픈소스가 아무리 열심히 한다고 해도, 제대로 된 자본의 투입이 없다면 그만큼 제대로 된 무언가를 만들기가 쉽지 않습니다. 회사에서 재정 후원을 받든, 아니면 별도로 재단을 설립하든 간에 어쨌든 꾸준히 돈을 쏟아부어야 됩니다. 이미 절대강자가 있는 이 시장에 굳이 최소 몇 년 이상 돈을 꾸준히 쏟아부을 회사들이 있을지...... 솔직히 전 없을 것 같습니다.

누가 그렇게 썼더군요. 자기 오픈소스 프로젝트에 Qt LTS가 제공되지 않아서 불안해서 못쓰겠다고...... 전 거기다 대고 한마디 하고 싶습니다. 거 LTS 제공 안하는 라이브러리들은 불안해서 어떻게 쓰신데요?

Wednesday, November 18, 2020

Strangely beautiful SQL UPDATE (PostgreSQL 12)

When you need to fill data to null fields of multiple records, you had better avoid UPDATE. Rather:

  1. Prepare for a table to insert all the data to fill
  2. Use SELECT ~ OUTER JOIN to join with the original table
  3. And prepare for the "result" table to push the JOINed results via INSERT INTO.

I found out that I could finish the job in 5:35 when traditional UPDATE didn't finish in 10 minutes.

Tested under PostgreSQL 12.


이상하고 아름다운 SQL UPDATE (PostgreSQL 12)

대량의 레코드를 대상으로 빈 필드에 데이터를 추가로 넣는 작업을 할 경우, UPDATE로 각 레코드를 찾아서 갱신하는 것보다

  1.  수정할 데이터를 삽입할 테이블을 하나 더 생성해서 추가 데이터를 여기에 넣은 뒤에
  2.  SELECT ~ OUTER JOIN으로 원래 테이블과 합치고 
  3. 결과를 INSERT INTO로 별도 테이블로 밀어넣는게 훨씬 빠릅니다.

UPDATE로 10분 넘게 걸려도 안 끝나던게 이 방법을 쓰니까 5분 35초만에 끝나네요. -o-;

PostgreSQL 12로 테스트해봤습니다

Saturday, September 19, 2020

Short thoughts about PostgreSQL(+a few lines for SQLite)

For a few days ago, I started to use PostgreSQL. Current development is not using database as it becomes I/O bottleneck, so I merely write down everything from RAM to disk at once(and additionally it is compressed in LZ4 to shrink write time). The problem was, that RAM usage skyrocketed as target data becomes TBs. Just guess what you feel when you encounter out of memory after 10 minute operation. What's worse, since I was working on portable server, the total amount of RAM was limited to certain degrees, not like rack-type servers.

So, I changed the mind model - use DB like as something like swap file! Currently the process is processing all the data on robin hood hash, but if we can control them under a database table and remove that table after use, we can save RAM dramatically(well, at least out of memory won't be shown if we limit working memory for DB).

At first, I tried SQLite. It consumes so low on RAM so that even Android and iOS can use, and before that I developed a class optimized for bulk insert with the engine. However, soon I found that it consumes practically infinite time when the query is even slightly complicated. When I tried an inner join query with two tables with 7 million records each, I gave up after 30 minutes. And it couldn't process in parallel since the engine is working only on single thread.

Later, I found out that SQLite uses only nested loop on table joins. If we're working on previous case(two tables with 7 million records each), we need to loop for 49 trillion times. And it should work on single thread. Oh God.

As I was cornered and forced to use "ordinary" database, my choice was MySQL, MariaDB, and PostgreSQL. MySQL was dropped at early stage as no tweak and tuning seems to have worked as I intended, so my choice became more limited. And I chose PostgreSQL for its a few exceptional features:

  1. It's not snail-slow as people say. Instead, if you tweak just few, it "flashes." No, I can't agree with the words "it's slower than MySQL or MariaDB." When you're working hard to tweak MySQL/MariaDB to maximize its performance with your complicated tweak but if you don't do same amount of efforts and try PostgreSQL just out-of-box, well, it's not fair. Isn't it?
  2. Parallel query processing! If you send a query the scheduler "smartly" judges and multi processing is done as needed. For my inner join of two tables with 7 million records, it consumed 3 cores and gave result in 1 minute 30 seconds.
  3. It's simply reliable in any bad condition. After stress test, PosgreSQL survived and gave result when MariaDB service was restarted

Currently I'm using it as if it's some kind of hashmap. Well, maybe that's because I previously worked with them. Thinking that the basis of database indexing is hashmap, it may be one of the best practice for any SQL based database. Additionally, my coding became simplified since database does  most of the things automatically......

Now I agree with the slogan, The World's Most Advanced Open Source Relational Database. Keep up the good work, PostgreSQL Global Development Group!

PostgreSQL에 대한 주절주절(+SQLite 이야기 약간)

얼마 전부터 PostgreSQL을 사용하기 시작했습니다. 현재 개발중인 시스템은 I/O 속도 문제때문에 DB를 쓰지 않고 RAM 구조를 통채로 디스크에 복사해뒀다가 필요할때 다시 읽어들이는 방식을 쓰고 있는데(그리고 그 와중에 디스크 쓰기 시간을 줄이기 위해 데이터를 LZ4로 미리 압축해 놓습니다), 처리하는 데이터의 양이 TB급으로 늘어나기 시작하면 RAM 사용량이 기하급수적으로 늘어나더군요. 10분정도 작업 수행하다 말고 out of memory를 만나면 참 난감해지더군요. 게다가 프로그램이 돌아가는 환경이 포터블 서버라 랙타입 서버처럼 RAM을 무식하게(......) 꽂을 수도 없는 일이고......

해서, DB를 swap file처럼 쓰는 방식을 생각해냈습니다. 지금은 모든 데이터를 robin hood hash에 올려서 처리하는데, 그 대신 DB에 필요한 데이터를 넣어두고 결과를 처리한 후 처리가 끝나면 테이블을 삭제하는 형태로 쓰면 RAM을 획기적으로 절약할 수 있겠다는 결론을 내렸습니다(아니 working memory를 제한하면 최소한 out of memory는 안 내놓겠죠 -_-).

처음에는 SQLite를 적용해 보았습니다. Android와 iOS에서 사용할 정도로 RAM 사용량이 적고, 마침 예전에 SQLite를 이용해서 bulk insert를 할 수 있는 별도의 클래스를 만들어 두기도 했거든요. 하지만 SQLite는 쿼리가 조금만 복잡해져도 수행시간이 거의 무한하게 늘어지는(......) 문제점이 있었습니다. 레코드 7백만개짜리 테이블 두 개에 대해서 간단한 inner join을 수행해 보니, 수행을 시작한지 30분이 지나도 결과가 나오지 않더군요. 게다가 single thread에서 동작할 수밖에 없는 특성상 처리를 병렬로 수행할 수도 없었고......

왜 그러나 확인해보니, SQLite는 table join에서 nested loop만을 사용하더군요. 7백만 개짜리 테이블 두 개라면 loop를 49경번(......)을 돌아야 된다는 이야기죠. 그것도 싱글 스레드로. '이건 아니지 않느냐'는 생각에 깔끔하게 포기했습니다.

결국 본격적인(?) DB를 적용할 수밖에 없게 된 상황에서, 선택지는 MySQL, MariaDB, PostgreSQL의 세 가지 정도로 좁혀지더군요. 개중에 MySQL은 아무리 튜닝을 해도 제가 원하는데로 움직여주지 않아서 일찌감치 탈락(-_-)시키고, MariaDB와 PostgreSQL을 비교해본 뒤 PostgreSQL을 선택했습니다. 이유가 몇가지 있는데......

  1. 굼뜨다는 세간의 평가와 달리, 설정을 조금만 건드려주면 말 그대로 날아다닙니다. 솔직히, MySQL이나 MariaDB보다 느리다는 말에는 동의하지 못하겠습니다. MySQL/MariaDB에서는 InnoDB 속도 향상을 위해서 엄청나게들 tweak을 하시면서 PostgreSQL에서는 그런 작업을 하지 않으면서 느리다는 평가를 내리는건 어불성설일 겁니다
  2. 단일 쿼리를 병렬로 수행합니다. 쿼리를 보내면 스케줄러가 알아서 판단해서 필요에 따라 멀티프로세싱으로 작업을 수행합니다. 제가 수행했던 저 7백만개 레코드 테이블 두 개의 inner join은 core 3개를 점유한 뒤 1분 30초만에 답을 내놓더군요.
  3. 그 어떤 악조건에서도 의지가 됩니다. 제가 부하 테스트를 여러번 해본 결과, MariaDB에서 프로세스가 터져버리는 상황에서도 PostgreSQL은 끝까지 기다려주면 무조건 결과를 내놓았습니다

원래 hashmap에서 작업을 수행해서 그런지는 모르겠지만, 지금도 DB를 hashmap 비슷하게 사용하고 있습니다. 뭐, DB indexing의 기본이 hashmap인걸 생각해보면 적절한 용례일지도 모르겠네요. 그 외에, DB가 상당부분을 알아서 처리해주는 터라 프로그램의 주 business logic이 간편해진 것은 덤이고......

이래저래, The World's Most Advanced Open Source Relational Database라는 슬로건에 부합하는 코끼리 파워를 느껴보았습니다. 앞으로도 계속 승승장구하길 기원합니다.

Friday, August 7, 2020

Qt Quick Controls: currentIndex won't change if model is statically generated on Tumbler + workaround

After a long time I reported a bug to Qt as I found quite unexpected bug from Qt Quick. On Qt Quick Controls 2, if you set model statically in Tumbler component(e.g. model: [2, 4, 6, 7, 10]), everything is shown properly but currentIndex doesn't change at all. For example, even though the screen shows the fifth item, currentIndex is still 0, and onCurrentIndexChanged event doesn't fire at all.

To work around this bug, you may just set a model "dynamically" like as follows:

Components.onCompleted: {
     // do something
     model=[ 1, 2, 3, 4, 5 ]
     // do something more
}

My check indicates this bug existed since Qt 5.12 or earlier, but nobody seems to have reported this, though I think there must be people using Tumbler like I do...... Well, maybe everyone was too lazy to write a report.

Anyway I reported the bug, and got ticket number QTBUG-85933.
https://bugreports.qt.io/browse/QTBUG-85933

 I hope that the bug can be fixed sooner.

Qt Quick Controls: Tumbler에 model을 정적으로 생성하면 currentIndex가 변경되지 않는 문제+우회방안

오랜만에 Qt 버그리포트를 하나 작성했습니다. Qt Quick에서 황당한 버그가 하나 있더군요. Quick Controls 2의 Tumbler에서 model을 정적으로 미리 설정해 두면(이를테면 model: [2, 4, 6, 8, 10]같은 형태로......) 화면은 제대로 작동하는데 내부에서 자기가 어느 데이터를 가리키고 있는지를 찾지 못하는 문제가 있습니다. currentIndex가 죽었다 깨나도 안 변하더군요. 분명히 화면은 다섯번째 아이템을 가리키고 있는데, currentIndex는 0으로 그대로이고, onCurrentIndexChanged 이벤트도 발생을 하지 않는다거나 하는 문제가 있습니다.

이 문제를 우회하려면, model을 동적으로 넣어주면 됩니다. 이를테면 이런 식으로요:

Components.onCompleted: {
// do something
model=[ 1, 2, 3, 4, 5 ]
// do something more
}

확인해보니 최소한 Qt 5.12부터 있던 버그인데, 아무도 신고하지 않은 것 같더군요. 분명 저처럼 쓸 일이 다들 있을 것 같기도 한데 말이죠...... 아니면, 어쩌면 다들 귀찮아서 말았을지도 모르겠네요. 

하여간 신고했고, 티켓 QTBUG-85933를 받았습니다.
https://bugreports.qt.io/browse/QTBUG-85933

이 버그는 언제쯤 해결될 수 있을지 모르겠네요.