2013년 2월 12일 화요일

Redis hash command

*  HDEL key field [ field ... ]
 Time complexity: O(N) - N은 지우려는 field의 수

1. key를 찾는다.
2. key의 value인 hash object에서 field를 지운다.
3. hash object에서 모든 element를 지웠다면 key도 지운다.

- 리턴
1.  key가 없으면 ":0\r\n"을 리턴
2.  실제로 지운 field의 개수를 리턴

* HEXISTS key field
Time complexity: O(1)

1. key를 찾는다.
2. field가 있는지 없는지 확인한다.

- 리턴
1. key가 없으면 ":0\r\n"을 리턴
2. field가 있으면 ":1\r\n"을, 없으면 ":0\r\n"을 리턴

* HGET key field
Time complexity: O(1)

1. key로부터 field를 찾는다.
2. field로부터 value를 찾는다.

- 리턴
1. key가 없거나 field가 없으면 "$-1\r\n"을 리턴
2. field에 해당하는 value를 리턴

* HGETALL key
Time complexity: O(N) - N은 hash의 size

1. key를 찾는다.
2. key의 value인 hash object에 있는 element의 수를 계산한다.
3. hash object를 처음부터 끝까지 순회하며 file와 value를 리턴할 버퍼에 넣는다.

- 리턴
1. key가 없으면 "*0\r\n"을 리턴
2. key에 속한 모든 field의 value를 리턴: 앞에는 element의 개수*2를 주고, 이후에 field, value의 순서로 표시한다.

* HINCRBY key field increment
Time complexity: O(1)

1. field의 값이 integer이면 increment만큼 증가시킨다.
2. increment는 양수/음수가 가능하다.

- 리턴
1. field로 저장된 값이 integer가 아니면 아무것도 리턴하지 않는다.
2. integer이면 변경된 값을 리턴

* HINCRBYFLOAT key field increment
 Time complexity: O(1)

1. HINCRBY와 같으나 field의 value가 float라는 것만 다르다.

* HKEYS key
Time complexity: O(N) - N은 hash의 size

1. key를 찾는다.
2. key의 value인 hash object에 있는 element의 수를 계산한다.
3. hash object를 처음부터 끝까지 순회하며 field를 리턴할 버퍼에 넣는다.

- 리턴
1. key가 없으면 "*0\r\n"을 리턴
2. key에 속한 모든 field를 리턴: 앞에는 element의 개수를 주고, 이후에 field를 표시한다.

* HLEN key
Time complexity: O(1)

1. key의 value인 hash object에 있는 element의 수를 계산한다.

- 리턴
1. key가 없으면 ":0\r\n"을 리턴
2. key에 속한 field의 개수를 리턴 -> 예) 5개인 경우 - ":5\r\n"

* HMGET key field [ field ...]
Time complexity: O(N) - N은 가져오려는 field의 수

1. key를 찾는다.
2. key의 value인 hash object에서의 field로부터 value를 찾는다.

- 리턴
1. 앞에 가져온 field의 개수를 표시하고, 이후에 field에 맞는 value를 표시한다.

* HMSET key field value [ field value ...]
Time complexity: O(N) - N은 설정하려는 field의 수

1. key를 찾는다.
2. key의 value인 hash object에 field/value를 저장한다.(HSET참조)

- 리턴
1. field/value의 쌍이 맞지 않으면 에러 리턴
2. 정상적으로 설정했을 경우 "+OK\r\n"을 리턴

* HSET key field value
Time complexity: O(1)

1. key가 있는지 확인 후 없으면 새로 만든다. 이때 key에 대한 value는 zip list를 가지는 hash object이다.
2. field나 value의 크기가 zip list에 넣을 수 없을만큼 크면 zip list로 저장하고 있던 것을 hash table로 바꾼다.
3. hash table로 저장되어 있는 경우라면 string이 integer로 변환될 수 있는지 확인하여 가능하다면 변환한다. -> 공간 절약
4. key의 value인 hash object에 field와 value를 저장한다. -> field가 hash object의 key
5. field가 이미 있으면 덮어쓴다.

- 리턴
1. 기존에 저장되어 있는 key의 type이 REDIS_HASH가 아니면 wrong type error 를 리턴한다.
2. field가 새로 저장된 것이면 ":1\r\n"을 리턴
3. field가 이미 있어서 덮어 쓴 것이면 ":0\r\n"을 리턴

* HSETNX key field value
Time complexity: O(1)

1. key를 찾는다.

- 리턴
1. field가 있으면 아무것도 저장하지 않고 ":0\r\n"을 리턴
2. field가 없으면 hash object에 field/value를 저장하고 ":1\r\n"을 리턴

* HVALS key
Time complexity: O(N) - N은 hash의 size

1. key를 찾는다.
2. key의 value인 hash object에 있는 element의 수를 계산한다.
3. hash object를 처음부터 끝까지 순회하며 value를 리턴할 버퍼에 넣는다.

- 리턴
1. key가 없으면 "*0\r\n"을 리턴
2. key에 속한 모든 field의 value를 리턴: 앞에는 element의 개수를 주고, 이후에 value를 표시한다.

------------

1. lookupKey: redisDb로부터 key를 찾는다. 찾으면 value를 리턴하고 못찾으면 NULL을 리턴한다.
2. expireIfNeeded: expire 시간이 지났으면 key를 지운다.
3. propagateExpire: slave와 AOF 파일에도 key를 지운다.
4. lookupKeyWrite/lookupKeyRead: expireIfNeeded를 호출한 후 lookupKey를 호출한다. read의 경우는 통계정보를 저장해준다.
5. hashTypeLookupWriteOrCreate: key를 찾아보고 없으면 새로 만든다. 이미 있는 경우는 key의 type이 REDIS_HASH인지 확인한다.
6. hash object를 처음 만드는 경우 zip list로 만든다. zip list에는 제약(string의 크기, list의 개수 등...)이 있으므로 사용중 zip list로 저장할 수 없는 값이 생기면 hash table로 바꿔준다.

2013년 2월 7일 목요일

ChatManager - Smack

1. 상대방 user id와 이에 대한 MessageListener로 Chat 방 하나가 만들어짐

* thread id를 만들어 관리함
** thread id: 숫자와 문자로 된 5개의 문자 + 0에서 시작하여 만들때마다 1씩 증가하는 number
* thread id, user id, base user id 이렇게 세개의 map을 통해 chat을 관리함
** 예: 5ab3et5, google@gmail.com/resource, google@gmail.com
** 왜 굳이 이렇게 3개를 가지고 chat을 관리할까? base user id 하나만으로도 충분하지 않나?

2. 상대방으로 부터 메시지가 오는 경우 chat 찾기

* thread id 확인 ->(없으면) user id로 chat 찾기 -> (없으면) chat 새로 생성
                        ->(있으면) thread id로부터 chat 찾기 ->(없으면) user id로부터 chat 찾기 -> (없으면) chat 새로 생성
* chat을 찾으면 여기에 message 전달

3. connection에 PacketListener와 PacketFilter를 전달하여 필요한 message를 전달받음

* PacketFilter의 accept함수가 true를 리턴하는 경우의 패킷이 있으면 PacketListener의 processPacket 함수를 호출함(PacketReader에서 이 작업을 해줌)

4. chat을 생성했을 때의 이벤트를 받기위한 ChatManagerListener를 등록할 수 있음

* 이 리스너를 등록하면 chat이 생성되었을 때 chatCreated 함수가 호출됨.

5. 패킷을 보낼 때 intercept하여 패킷에 변형을 가할 수 있도록 해주는 PacketInterceptor를 등록할 수 있음

* PacketFilter와 쌍으로 동작하여 PacketFilter의 accept 함수가 true를 리턴하는 경우 PacketInterceptor의 interceptPacket 함수를 호출함 - 이 함수 안에서 message의 변형을 가하면 됨

6. 지정한 패킷이 왔을 때 호출되도록 할 수 있는 PacketCollector를 제공함

* ChatManager가 제공하는 PacketCollector는 thread id 와 상대방 user id가 맞으면 호출되는 collector

ReferenceMap : id와 chat 관리에 사용 - weak reference
CopyOnWriteArraySet : ChatManagerListener에 사용
WeakHashMap : PacketInterceptor 관리에 사용

2013년 2월 6일 수요일

PacketWriter and PacketReader in Smack

- PacketReader

1. XML 파싱을 위해 XMLPullParser 를 사용
2. done 이라는 boolean 값을 두어 필요시 reading을 끝낼 수 있도록 한다.
3. 서버로부터 오는 패킷을 읽기 위해 쓰레드 생성
4. 서버와의 처음 연결시 응답이 오는 것의 확인을 위해  Semaphore(1) 사용
  semaphore 획득 -> timeout을 가지고 재획득 요청
  -> 지정한 timeout안에 응답이 오면 앞의 reading을 위한 thread에서 semaphore를 release함 -> connectionID 설정
  -> 지정한 timeout안에 응답이 오지 않으면 semaphore 획득에 실패 -> 에러 처리
5. 서버로부터 패킷이 오면 이를 처리하기 위해 ExecutorService를 사용
  쓰레드의 관리(tracking)를 편하게 하기 위해 ExecutorService를 사용함

- PacketWriter

1. 패킷을 보내는 용도로 사용하는 쓰레드 생성
2. 패킷을 보내는 경우 바로 writer를 통해 보내지 않고 queue(ArrayBlockingQueue)에 데이타를 넣는다.
3. 처음 시작시 서버와의 연결을 위한 패킷을 보내고 queue에 데이타가 들어오기를 기다린다. 데이타가 들어오면 깨어나서 패킷을 처리한다.
4. 서버와의 접속 유지를 위해 keep alive 패킷(white space)을 보낸다. 이를 위해 별도의 쓰레드를 생성한다. 이 쓰레드는 지정한 시간 sleep 후 패킷 보내고 다시 sleep하는 것을 반복한다.
5. writer 변경을 위한 setWriter 함수를 제공한다.


Redis Log

- 로그

1. 로그 레벨
conf에 아래의 로그레벨을 설정하여 이보다 작은 값의 경우는 로그하지 않도록 한다.

REDIS_DEBUG 0
REDIS_VERBOSE 1
REDIS_NOTICE 2
REDIS_WARNING 3
REDIS_LOG_RAW (1 << 10) // 혼자 사용되지 않고 위의 값과 함께 사용됨

2. 기본적으로 redisLog 호출 -> 내부에서 redisLogRaw를 호출함

3. redisLog

printf같은 형태로 로그에 출력할 메시지를 설정하게 해주는 함수
로그할 메시지의 내용을 만든 후 redisLogRaw를 호출함

4. redisLogRaw

* conf의 logfile에 파일 이름이 지정되어 있으면 그 파일에 로그 출력하고 없으면 stdout에 출력한다.
* REDIS_LOG_RAW가 로그레벨에 포함되어 있으면 메시지만을 출력하고 그렇지 않으면 'pid, tlrks, log level, 메시지'의 형태로 출력한다.
* conf에 syslog_enabled가 설정되어 있으면 syslog에도 출력한다.

redis의 log level에 대응되는 syslog의 priority는 다음과 같다.

LOG_DEBUG : REDIS_DEBUG
LOG_INFO : REDIS_DEBUG
LOG_NOTICE : REDIS_NOTICE
LOG_WARNING : REDIS_WARNING

Redis command - 1

* GET key
Time complexity: O(1)
key의 값(value) 얻어오기. key가 없으면 nil을 리턴
GET은 string만을 다루기 때문에 key의 값이 string이 아니면 에러가 리턴됨

- getCommand in t_string.c
a. db로부터 key를 찾는다.
b. key가 있으면 access time을 업데이트
c. key가 없으면 "$-1\r\n"을 client에 보낸다.
d. 찾은 key의 type이 string이 아니면 "-ERR Operation against a key holding the wrong kind of value\r\n"을 client에 보낸다.
e. 리턴할 value의 크기를 prefix로 해주고(예: "$2344\r\n") 뒤에 value를 붙인 후 client에게 보낸다.(예: value가 Hello인 경우 - "$5\r\nHello\r\n")

- value의 크기가 32보다 작은 경우는 자주 사용되므로 미리 shared.bulkhdr[]에 보낼 값을 만들어 놓고 사용함
shared.bulkhdr[j] = createObject(REDIS_STRING, sdscatprintf(sdsempty(), "$%d\r\n", j));

* SET key value
Time complexity: O(1)
string인 value를 key로 하여 저장. key가 이미 있으면 그 값의 타입이 무었인지에 상관없이 덮어쓴다.

- setCommand in t_string.c
a. 공간 절약을 위해 string이 숫자이면 숫자로 변환한다.
b. db에 key/value를 무조건 설정한다.(없으면 add, 있으면 overwrite)
c. expire가 설정되어 있으면 delete
d. 이 key를 watch하고 있는 client가 있으면 이 client의 flags값에 REDIS_DIRTY_CAS를 더함으로서 그 client에서의 exec가 실패하도록 해준다.
e. 항상 "+OK\r\n"을 client에게 보낸다.(set은 실패하지 않음)

* SETNX key value
Time complexity: O(1)
key가 없을 경우만 key/value를 저장한다.

- setnxCommand in t_string.c
a. key가 없을 경우는 SET과 같다.
b. key가 있을 경우 ":0\\r\n"을 key가 없을 경우는 ":1\r\n"을 client에게 보낸다.

* SETEX key seconds value
Time complexity: O(1)
주어진 시간에 timeout하는 key/value 저장
아래의 두 명령을 MULTI/EXEC block 안에서 실행하는 것과 같은 명령
    SET key value
    EXPIRE key seconds

- setexCommand in t_string.c
위의 SET 명령어에 더하여 expire에 관련된 부분만 추가해줌
a. seconds를 milliseconds로 바꾼다.
b. db의 expires로부터 key를 찾는다.(없으면 추가)
c. key의 value를 milliseconds로 바꾸어준다.

* PSETEX key milliseconds value 
Time complexity: O(1)
expire time을 millisecond로 설정한다는 것만 제외하고는 setex와 같은 명령어

* APPEND key value
Time complexity: O(1)
key가 이미 있고 string이라면 string의 끝에 value를 더한다. 없으면 key를 만들어서 value를 넣는다.

- appendCommand in t_string.c
a. key를 찾는다.
b. 없으면 key를 만들어 value를 저장한다.
c. key가 있으면 type을 확인하여 type이 string이 아니면 "-ERR Operation against a key holding the wrong kind of value\r\n"을 client에게 보낸다.
d. append할 경우 string의 size가 512MB를 넘으면 "string exceeds maximum allowed size (512MB)"를 client에게 보낸다.
e. object가 shared이거나 encoded이면 value를 copy한 후 key의 value에 덮어쓴다.
f. key의 value가 바뀌었으므로 watch하고 있는 client에 signal을 날린다.

* STRLEN key
Time complexity: O(1)
key에 저장된 value의 size를 리턴한다. key가 string이 아니면 에러를 리턴한다.

- strlenCommand in t_string.c
a. key가 없으면 ":0\r\n"을 client에게 보낸다.
b. key가 string이 아니면  "-ERR Operation against a key holding the wrong kind of value\r\n"을 client에게 보낸다.
c. value의 크기를 client에게 보낸다.

* DEL key [key ...]
Time complexity: O(1)
지정한 키를 지운다. key가 없으면 무시한다.

- delCommand in db.c
a. argument로 지정한 key들을 지운다.
b. 지정한 key를 watch하고 있는 client에게 알린다.
c. 지운 key의 수를 client에게 보낸다.

* EXISTS key
Time complexity: O(1)
key가 존재하는지 확인한다.

- existsCommand in db.c
a. key가 존재하는지 확인한다.
b. 있으면 ":1\r\n"을 client에게 보낸다.
c. 없으면 ":0\r\n"을 client에게 보낸다.

* SETBIT key offset value
Time complexity: O(1)
key에 저장된 value의 offset위치의 bit를 set(1) 또는 clear(0)한다.

- setbitCommand in bitops.c
a. offset의 값을 가져온다. 512MB이하여야 한다. 아닐경우 "-ERR bit offset is not an integer or out of range\r\n"를 리턴한다.
b. value의 값을 가져온다. LONG_MIN과 LONG_MAX사이여야 한다. 아닐 경우 "-ERR bit is not an integer or out of range\r\n"를 client에게 보낸다.
c. offset이 0이나 1이 아니면 "-ERR bit is not an integer or out of range\r\n"를 client에게 보낸다.
d. key를 찾는다.
e. key가 없으면 empty string을 새로 만든다.
f. key가 있으면 type이 string인지 확인한다. 그리고 나서 key가 shared 또는 encoded이면 copy를 하여 기존의 다른 값이 변하지 않도록 한다.
g. string의 크기가 offset만큼 크지 않으면 offset 설정이 가능하도록 string의 크기를 증가시킨다. 이때 추가되는 부분의 bit값은 0으로 한다.
h. offset 부분의 값을 value로 바꾼다.(key의 값이 바뀌므로 watch중인 client에 signal을 보낸다.)
i. 값을 바꾼 bit의 원래 값을 client에게 보낸다.

* GETBIT key offset
Time complexity: O(1)
key에 저장된 value의 offset 위치의 값을 리턴한다.

- getbitCommand in bitops.c
a. offset 값이 정상적인지 확인한다.
b. key가 없으면 ":0\r\n"을 client에게 보낸다. type이 string이 아니면 에러를 리턴한다.
c. key가 있으면 offset 위치의 값을 client에게 보낸다.(":1\\r\n" or ":0\r\n")
d. offset의 위치가 key의 string 크기를 벗어나면 0으로 가정하고 값을 리턴한다.

* SETRANGE key offset value
Time complexity: O(1)
string의 부분을 변경한다.

- setrangeCommand in t_string.c
a. offset값을 가져온다. 값이 0보다 작으면 "-ERR offset is out of range\r\n"을 client에게 보낸다.
b. key를 찾는다.
c. key가 없는 경우 다음의 순서로 진행한다.
    c-1. value의 크기가 0이면 ":0\r\n"을 client에게 보낸다.
    c-2. offset + value의 크기가 최대 크기를 넘으면 에러를 리턴한다.
    c-3. key에 빈 스트링을 넣는다.
d. key가 있는 경우
    d-1. key의 타입이 string인지 확인한다.
    d-2. value의 크기가 0이면 저장되어 있는 key의 value의 크기를 리턴한다.
    d-3. offset + value의 크기가 최대 크기를 넘으면 에러를 리턴한다.
    d-4. object가 shared이거나 encoded이면 object를 복사한다.
e. value의 크기가 0보다 크면 key의 value의 offset위치의 값을 변경한다. 이때 offset위치가 저장되어 있는 value의 크기보다 크면 0(zero byte)을 붙여서 값 변경이 가능하도록 해준다.
f. 변경된 value의 크기를 리턴한다.

* GETRANGE key start end
Time complexity: O(N)
string의 지정한 부분의 값을 가져온다. 음수의 start와 end는 뒤에서 부터를 의미한다. 이전 버전에서는 SUBSTR이었다.(코드에 남아있으므로 사용은 가능하다.)

- getrangeCommand in t_string.c
a. key가 없으면 "$0\r\n\r\n"를 리턴한다.
b. start가 음수인 경우 뒤에서 부터 value에서의 실제 위치를 계산했을 때 value의 0 index보다 더 작게 가면 0으로 설정한다.
c. end가 음수인 경우 뒤에서 부터 value에서의 실제 위치를 계산했을 때 value의 0 index보다 작으면 0으로 설정한다.
d. end가 value의 크기보다 크면 값을 value의 크기로 조정한다.
c. end보다 start가 더 크면 "$0\r\n\r\n"를 리턴한다.

Building asynchronous views in SwiftUI 정리

Handling loading states within SwiftUI views self loading views View model 사용하기 Combine을 사용한 AnyPublisher Making SwiftUI views refreshable r...