프로그래밍 일반 2016. 8. 22. 14:52

FileSystemWatcher Changed 이벤트 중복 호출 버그

FileSystemWatcher 클래스는 특정 폴더나 파일 변경 모니터링이 필요할 때 사용하는데 Changed 이벤트에 버그가 있다. 


버그 해결이 까다로운데 이유는 해당 이벤트는 파일 변경이 종료되었음을 알리는게 아니기 때문에 종료 시점을 프로그래머가 꼼수를 써서 알아내야한다.


서버 환경 설정이 셋팅된 파일을 모니터링하다가 변경되면 실시간으로 반영하도록 작업되어 있는데 모니터링 파일이 커지면 Changed 이벤트가 


여러번 발생한다.


4,540 바이트 파일을 모니터링할 때 파일 복사가 종료될 때까지 LastWrite 필터는 5회, LastAccess 필터는 3회의 이벤트가 발생한다.


이벤트 중복 호출이 문제가 되는 이유는 일반적으로 변경 이벤트 발생 시 관련 핸들러에 해당 파일을 읽어 들이는 작업을 작성할텐데 파일 쓰기 


작업이 완료되지 않은 상황에 이벤트가 발생하여 파일에 동시 접근을 시도하기 때문에 변경 내용을 읽어들일 수 없게된다.


아래의 코드는 Changed 이벤트 버그를 해결할 수 있는 C#으로 작성한 간단한 예이다.  붉은색으로 작성한 부분이 중요 포인트


    /// <summary>

    /// 지정된 경로의 파일이나 폴더가 변경되면 이벤트를 발생 시킨다.

    /// </summary>

    public static class FileWatcher

    {

        /// <summary>

        /// 파일 시스템 모니터링 객체

        /// </summary>

        static FileSystemWatcher watcher = new FileSystemWatcher();



        /// <summary>

        /// 리소스 해제

        /// </summary>

        public static void Dispose()

        {

            watcher.Dispose();

        }


        /// <summary>

        /// 파일 시스템 이벤트 핸들러를 생성한다.

        /// </summary>

        /// <param name="dataDirectory">공통 csv 관리 폴더 경로</param>

        public static void CreateHandler(string dataDirectory)

        {

            if (false == watcher.EnableRaisingEvents)

            {

                watcher.Path = string.Format("{0}\\SystemConfig", dataDirectory);

                watcher.NotifyFilter = NotifyFilters.LastAccess | NotifyFilters.Size;


    // 하나의 파일만 모니터링하고 있다.

                watcher.Filter = "ContentsConfig.ini";


                watcher.Changed += new FileSystemEventHandler(OnChanged);

                

                watcher.EnableRaisingEvents = true;

                watcher.IncludeSubdirectories = false;

            }

        }


        /// <summary>

        /// 지정된 경로에서 파일이나 디렉토리가 변경될 경우 발생하는 이벤트

        /// </summary>

        /// <param name="source"></param>

        /// <param name="eventArgs"></param>

        private static void OnChanged(object source, FileSystemEventArgs eventArgs)

        {

            try

            {

                // 모니터링을 비활성화, 해당 방법은 하나의 파일만 모니터링할 때 사용 가능하다.

                watcher.EnableRaisingEvents = false;


                if (WatcherChangeTypes.Changed != eventArgs.ChangeType)

                {

                    return;

                }


                // 파일 변경 모니터링 핸들러 진입


                // 서버 컨피그 파일만 처리한다.

                if (true == string.Equals(

                    "모니터링 파일 명",

                    eventArgs.Name,

                    StringComparison.OrdinalIgnoreCase))

                {

                    // 모니터링 파일이 큰 경우 FileSystemWatcher의 변경 이벤트가 중복 호출되는 버그와

                    // 변경 대응 이벤트가 무거운 경우 파일 액세스 동시 접근 오류가 발생한다.

                    // 동시 접근 오류를 해결하기 위해 쓰기가 완료되었는지 확인할 필요가 있다.

                    string fullpath = string.Format("{0}\\{1}", watcher.Path, SystemConfigDefine.ContentsConfigFileName);


                    // 파일 쓰기 중인지 확인한다.

                    while (true)

                    {

                        try

                        {

                            using (Stream stream = File.Open(fullpath, FileMode.Open, FileAccess.Read, FileShare.None))

                            {

                                if (null != stream)

                                {

                                    break;

                                }

                            }

                        }

                        catch (Exception exception)

                        {

                            LoggerWrapper.Information("[FileWatcher] Changing {0}, {1}", eventArgs.Name, exception.Message);

                        }


                        // 메인 스레드와 다른 스레드이기 때문에 슬립을 사용해도 다른 시스템에는 영향이 없다.

                        System.Threading.Thread.Sleep(1);

                    }


                    ... 이곳에 파일을 읽어들이는 코드를 작성한다.


         // 파일 변경 완료

                }


            }

            finally

            {

     // 모니터링 활성화

                watcher.EnableRaisingEvents = true;

            }


        }


SQL 2016. 8. 12. 16:45

MySql에서 내림차순 정렬 인덱스 사용하기

현재 프로젝트에서 사용 중인 MySql에서 내림차순 인덱스를 지원하지 않는다.


내림차순 정렬 후 페이지 단위로 셀렉트해야하는 경우 오름차순으로 정렬된 인덱스 때문에 내부적으로 불필요한 정렬이 계속 발생한다.


그러면 어떻게하면 되느냐.. 그냥 MSSql을 사용하자.. 


그럴 수 없다면 꼼수를 사용하면 되는데..



DateTime 형식의 내림차순 인덱스를 만든다고 한다면 위의 그림을 참고하자.



Min, Max는 DateTime 형식의 Min, Max 값을 생각하고 숫자 1, 2번은 데이터 발생 시점으로 생각한다면,


일반적으로 RegDateTime가 오름차순으로 구성되어 있을 것이고 (내림차순 인덱스가 지원되지 않는다) 필요한 데이터를 내림차순 정렬 후


페이지 단위로 셀렉트한다면 DBMS에서는 정렬 후 해당 페이지 단위만큼 셀렉트할 것이다.


당연히 복합 인덱스 컬럼이 오름차순으로 셋팅되어 있다면 이후에 발생한 2번이 두번째에 구성되는게 맞다. 


그러면 여기서 바라보는 시각을 비틀어 발생 시각이 아니라 Max까지 남은 시간을 생각해보면, 2번 데이터가 발생한 시점에 Max까지 


남은 시간 3번이 4번보다 더 적은 것을 알 수 있을 것이다.


답은 나왔다. 인덱스 컬럼을 3, 4번으로 사용하면 오름차순 인덱스만 지원하는 MySql의 특성을 만족 시키면서 필요한 데이터를 내림차순으로


얻을 수 있다. 


정렬 키를 만드는 C# 코드

TimeSpan spanTime = DateTime.MaxValue - DateTime.UtcNow;

DateTime orderByKey = DateTime.MinValue + spanTime;



정렬이 발생하지 않는 내림차순 페이지 단위 셀렉트 MySql 쿼리


SELECT

....

FROM table

WHERE Guild_SN = pGuildSN

ORDER BY OrderByKey

LIMIT pStartIndex, pPageSize;


DBMS에서 정렬이 발생하면 왜 안되는지는 찾아보면 아래 링크


링크 : 정렬이 왜 나빠?

담당 프로젝트 2015. 8. 4. 12:17

스펠나인




2015년 6월 1일 ~ 2016년 11월 11일,  스펠나인 개발팀


링크: https://play.google.com/store/apps/details?id=com.ftt.spell9.kr.gl&hl=ko


사용 언어 및 라이브러리 : C#,  asp.net, MySql, Redis


개발툴 : VS2013, SQLyog, MySQL Workbench 6.3, Perforce


간단한 업무 내용

- 컨텐츠에 대한 서버, DB 전반에 걸친 설계 및 구현

- 정산 시스템 제작

- 개발 편의 툴 작업



개발 컨텐츠 목록

- 보상 시스템

- 출석부 시스템

- 우편 시스템

- 실시간 이벤트 시스템

- 실시간 상점 시스템

- 친구 시스템

- 도전과제 시스템

- 아레나 시스템

- 길드 대전 시스템

- 아레나, 길드 대전 정산 시스템

- 서버 오류 수집 시스템