월별 글 목록: 2012년 2월월

Cassandra Valueless Column(값이 없는 컬럼) Pattern에 대해..

카산드라(Cassandra)의 값이 없는(Valueless Column) 패턴은 어렵진 않지만, 처음 보면 잘 이해가 안 갈 수도 있다. 현재 1.0.8 버전의 카산드라가 배포되는 시점에서 과연 이 패턴을 사용할까에 대한 의문(?)은 사용하고 있지 않을 것으로 확신할 수 있다. 이미 0.7 버전부터 보조 인덱스를 지원하기 시작했으니, 현 시점에서 이 패턴은 쓸모(?)가 없어 보인다.

그래도, 0.6 버전을 사용하는 곳이 있을 수도 있으니, 한번 알아나 보자. 값이 없는 컬럼에 대한 설명은 “카산드라 완벽가이드 번역서“에서 설명하고 있다.

역서의 100페이지에서 7번째 줄을 설명을 보자..

“User/UserCity 예제를 빌드해 보자. User..


..”

위의 내용은 번역서를 참고하시고, 아래에 설명을 하면

– USER 컬럼 패밀리

<<cf>>USER
Key: UserID
Cols: username, email, birth date, city, state

– USERCITY 컬럼 패밀리

<<cf>>USERCITY
Key: city
Cols: IDs of the users in that city.

위에 2개(USER, USERCITY)의 컬럼 패밀리가 있다는 것을 가정하면..


USER는 Key(고유키)를 가지고 있고, USERCITY의 Cols 컬럼 이름으로 USER의 Key를 가지는 형태가 바로 “값이 없는 컬럼” 패턴이다.

따라서, 위의 컬럼 패밀리에서 USERCITY 컬럼패밀리에 ‘Seoul’이라는 key가 있으면, Cols의 값이 없는 컬럼 이름들이, ‘Seoul’이라는 도시에 살고 있는 사람들이라는 내용이 되겠다.

Android java.util.concurrent.RejectedExecutionException 핸들링(2/2)..

앞에서 Android java.util.concurrent.RejectedExecutionException 핸들링(1/2).. 하기라는 포스팅에서 안드로이드에서 상당히 많은 쓰레드를 단기간에 실행(보통 안드로이드 개발가이드만 보면 유발 가능)시키면 RejectedExecutionException이 발생하고 이것에 대한 간단한 해결책을 기술했습니다. 그 내용과 더불어서 위 상황을 해결할 수 있는 방법으로, 좀 더 편리한 방법을 기술해 봅니다.

이 방법은 위 링크에서 설명하고 있는 ExecutorService가 실행하는 방식과 비슷한 Producer Consumer 패턴을 이용해서 해결할 수 있습니다. Producer는 간단하게 AsyncTask를 실행할 데이터를 Queue에 입력하는 코드가 되겠고, Consumer는 Queue에 들어간 데이터를 가져와서 AsyncTask를 실행하는 쓰레드입니다..

아래 코드는 실시간으로 바로 응답이 필요한 Task보다는, 한꺼번에 많은 Task를 Queue에 담았다가 약간 지연(위의 Exception 상황 회피)를 주면서 처리하는 코드에 유용하게 사용할 수 있습니다.

아래 PhotoDispatcher 클래스는, 위의 설명대로 Queue와 Consumer를 유지하면서, Queue에 처리할 데이터가 들어오면 Consumer가 Queue에서 데이터를 가져와서 AsyncTask를 실행시킵니다. ^^

public class PhotoDispatcher {
    static String CNAME = PhotoDispatcher.class.getSimpleName();
    static BlockingQueue<Element> queue = new ArrayBlockingQueue<Element>(500);
    static ArrayList<Thread> consumers = new ArrayList<Thread>(2); // 쓰레드 2개
   
    static {
        consumers.add(new Thread(new Consumer(“1”)));
        consumers.add(new Thread(new Consumer(“2”)));
       
        consumers.get(0).start();
        consumers.get(1).start();
    }
       
    public static void put(PhotoElement e) {
        if(e == null)
            return;
       
        try {
            queue.put(e);
        } catch (InterruptedException e1) {
            LogUtil.e(CNAME, LogUtil.TAG, ExceptionUtil.getException(e1));
        }
    } 
   
    public static class Consumer implements Runnable {
        private String name = “”;
        public Consumer(String name) {
            this.name = name;
        }
       
        @Override
        public void run() {
            while(true) {
                try {
                    Element element = queue.take();
                   
                    if(IS_DEBUG) {
                        LogUtil.w(CNAME, LogUtil.TAG, “NAME : ” + name);
                        LogUtil.w(CNAME, LogUtil.TAG, “ELEMENT : ” + element.toString());
                        LogUtil.w(CNAME, LogUtil.TAG, “URL : ” + AuthService.thumbnailServerUrl);
                    }
                   
                    if(element != null) {
                        // 썸네일 가져오는 Task
                        new GetThumbnailTask(element.c, element.iv, element.fileId).execute(element.fileId);
                    }
                   
                    // 쓰레드를 0.5초 딜레이 시킨다.
                    Thread.sleep(500);
                } catch(Exception e) {
                    LogUtil.e(CNAME, LogUtil.TAG, ExceptionUtil.getException(e));
                }
            }
        }
    }
}