레이블이 CLR Object인 게시물을 표시합니다. 모든 게시물 표시
레이블이 CLR Object인 게시물을 표시합니다. 모든 게시물 표시

2019년 7월 26일 금요일

How to encode/decode base62 using C# and Create MSSQL CLR Object

개발을 하면서 base10, base26, base36, base62, base64등으로 encoding, decoding을 하는 경우가 있다. 그리고 많은 서비스에서 shorten url을 만들 때는 base62로 처리한다. base64로 처리할 경우 특수문자가 들어가기때문에 숫자+알파벳 조합인 base62를 선호한다.

Base62에서 표현가능한 값
- ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789

가령 어떤 페이지의 원래 주소는 A이며 이 Url이 너무 길어서 base62로 encoding하여 xxx.li/R04R(Shorten url)로 만들었다. 이때 사용자들에게 접속을 유도한 주소는 xxx.li/R04R이고 사용자가 저 주소를 통해 접속하면 서버에서는 R04R을 Decoding하여 6435131(key)를 알아낸다. 그리고 그 key를 DB에서 조회하여 Full Url인 A라는 주소를 찾아낸 후 사용자를 A 주소로 접속하게 한다.

이번 포스팅에서는 C#으로 숏URL을 만들때 사용할 수 있는 base62 인코딩, 디코딩을 구현하고 더 나아가 CLR Obect로 만들어서 DB에서 인코딩/디코딩을 하는 것을 테스트해본다.

자료 링크 : https://github.com/parksuseong/Base62



using System;
using System.Data;
using System.Data.SqlClient;
using System.Data.SqlTypes;
using Microsoft.SqlServer.Server;

using System.Collections.Generic;
using System.Linq;
using System.Text;

public partial class StoredProcedures
{
    private static char Base62Digit(int d)
    {
        if (d < 10){
            return (char)('0' + d);
        }
        else if (d < 36){
            return (char)('A' + d - 10);
        }
        else if (d < 62){
            return (char)('a' + d - 36);
        }
        else{
            throw new ArgumentException("d");
        }
    }
    public static void EncodeBase62(SqlString n)
    {
        int n2 = int.Parse(n.ToString());
        var res = "";
        while (n2 != 0){
            res = Base62Digit(n2 % 62) + res;
            n2 /= 62;
        }
        SqlDataRecord record = new SqlDataRecord(new SqlMetaData("stringcol", SqlDbType.NVarChar, 128));
        record.SetSqlString(0, res);
        SqlContext.Pipe.Send(record);

    }
    private static int Base62Decode(char c)
    {
        if (c >= 'a' && c <= 'z'){
            return 36 + c - 'a';
        }
        else if (c >= 'A' && c <= 'Z'){
            return 10 + c - 'A';
        }
        else if (c >= '0' && c <= '9'){
            return c - '0';
        }
        else{
            throw new ArgumentException("c");
        }
    }
    public static void DecodeBase62(SqlString s)
    {
        SqlString res = "";
        string s2 = s.ToString();
        res = (s2.Aggregate(0, (current, c) => current * 62 + Base62Decode(c))).ToString();
        SqlDataRecord record = new SqlDataRecord(new SqlMetaData("stringcol", SqlDbType.NVarChar, 128));
        record.SetSqlString(0, res);
        SqlContext.Pipe.Send(record);

    }
}


이렇게 만든 프로그램을 실행하여 DLL을 만들었고 그 DLL을 활용하여 MSSQL CLR Object로 만들어보자.

인코딩용/디코딩용 프로시저를 각각 만들기로 한다.
먼저 ASSEMBLY를 생성하하고 각각의 프로시저에서 위에서 생성한 클래스의 알맞은 메서드들을 호출한다.


--drop proc USP_CLR_DECBASE62
--drop proc USP_CLR_ENCBASE62

-- Drop the assembly
IF EXISTS (SELECT * FROM sys.assemblies WHERE name = 'BASE62_CLR')
BEGIN
DROP ASSEMBLY BASE62_CLR
END
GO

-- Create the assembly
CREATE ASSEMBLY BASE62_CLR
FROM 'D:\mssqlClr\BASE62_CLR.dll'
WITH PERMISSION_SET = UNSAFE;
GO



CREATE PROCEDURE USP_CLR_DECBASE62 @STR NVARCHAR(MAX)
WITH EXECUTE AS CALLER AS EXTERNAL NAME BASE62_CLR.StoredProcedures.DecodeBase62
GO


CREATE PROCEDURE USP_CLR_ENCBASE62 @STR NVARCHAR(MAX)
WITH EXECUTE AS CALLER AS EXTERNAL NAME BASE62_CLR.StoredProcedures.EncodeBase62
GO



실제로 잘 동작하는지 확인해보자.

exec USP_CLR_DECBASE62 'R04R'


 --exec USP_CLR_ENCBASE62 '6435131'


잘 동작하는 것을 확인할 수 있다.


C# 코드, DB 스크립트, DLL 파일 다운로드 주소
https://github.com/parksuseong/Base62


2018년 10월 14일 일요일

MSSQL 어셈블리 만들기 - How to Implementing CLR Object DLL

시나리오
1. SQL Server에서 PDW(Parallel Data Warehouse)로 동적 쿼리를 만들어서 날리려고 하는데 8000자 이상의 쿼리문을 수행해야 한다.
2. A서버의 SQL Server (가정 주소:321.321.1.321,9999)에서 B서버의 PDW (가정 주소 : 123.123.1.123,8888)로 쿼리를 날려 SELECT 결과를 받아오고자 한다.
3. 특정 사용자에게만 DB 접근권한을 주고 계정을 분리하고자 한다.

해결방법
CLR Object DLL를 만들어서 ASSEMBLY로 등록하고 CLR을 통해 쿼리를 수행한다.
CLR을 통해 DB 접근 권한을 분리하고 8000자 이상의 동적쿼리를 쿼리를 수행할 수 있다.

  고려사항
얼마 전 MSSQL이 설치된 윈도우 서버에서 메모리가 부족한 현상을 겪었고 덕분에 수 번의 장애를 겪었다. 윈도우 서버의 메모리가 약 100기가 이상이 되지만 보통의 경우에는 DB에 대부분의 메모리를 할당하고 최소한의 메모리를 OS에 할당하겠지만 필자의 경우에는 MSSQL에서 PDW로 CLR을 호출하는 경우 딱 그 시점에 윈도우 서버의 메모리가 확 튀며 장애 현상을 겪었다. 이런 경우에는 MS 엔지니어가 메모리를 DB에 적당히 할당한 후 안전하다고 판단되는 수준까지 점진적으로 올릴 필요가 있다고 한다.


방법
1. DLL을 만들기 위해 Visual Studio에서 Visual C# CLR Database Project를 만든다.
(파일 -> 새 프로젝트 -> SQL Server -> SQL Server 데이터베이스 프로젝트 생성)


2. 솔루션 탐색기에서 추가 -> 새항목 -> SQL CLR C# -> SQL CLR C# 저장 프로시저 생성한다.





3. 쿼리를 날릴 때 SelectPdwByLogin 함수를 호출할 것이고 이 함수 내부에서 분리시키고자 하는 login_name을 "exuser"를 구분했다.



4. 빌드를 하고 DLL(CLR_PDW.dll)을 얻는다. 이를 서버(321.321.1.321,9999)의 적당한 위치에 복사한다.




5. 서버(321.321.1.321,9999)에서 CREATE ASSEMBLY문으로 CREATE ASSEMBLY를 하고
이 때 위에서 만든 CLR_PDW.dll의 경로를 입력해준다. 그리고 프로시저를 하나 생성하여 어셈블리를 수행하도록 한다.



6. 테스트를 한다.
A 서버 SQL Server(321.321.1.321,9999)에서 USP_CLR_PDW를 호출하여 B 서버의 PDW(123.123.1.123,8888)의 TEST DB CUSTOMER 테이블을 SELECT 해본다.




※ 별도로 test DB에는 로그인 생성, 사용자 추가, 롤에 사용자를 추가해야한다.

--로그인 생성
CREATE LOGIN exuser WITH PASSWORD = 'exuser',
CHECK_EXPIATION = OFF,
CHECK_POLICY = OFF;

--TEST DB에 사용자 추가
USE test;
CRATE USER exuser WITH DEFAULT_SCHEMA=dbo;

--롤에 사용자 추가
EXEC test..sp_addrolemember db_datareader, exuser;

2022년 회고

 올해는 블로그 포스팅을 열심히 못했다. 개인적으로 지금까지 경험했던 내용들을 리마인드하자는 마인드로 한해를 보낸 것 같다.  대부분의 시간을 MLOps pipeline 구축하고 대부분을 최적화 하는데 시간을 많이 할애했다. 결국에는 MLops도 데이...