로컬 하드 드라이브 기반 NFS 캐싱에 대해 들어본 사람이 있습니까? (주로 FreeBSD에서)

로컬 하드 드라이브 기반 NFS 캐싱에 대해 들어본 사람이 있습니까? (주로 FreeBSD에서)

질문:NFS를 통해 /usr/bin과 같은 바이너리를 부팅할 때(예: 네트워크 부팅 시스템에서) NFS가 느려질 수 있습니다. RAM 버퍼 캐싱은 속도 저하를 방지하기에 충분하지 않을 수 있습니다.

아이디어:NFS에서 파일을 가져오는 동안 로컬로 파일을 저장할 수 있는 로컬 디스크 캐시를 가질 수 있어야 하는 것 같습니다.

질문:UNIX 시스템에서 비슷한 것을 본 사람이 있습니까?

배경:

FreeBSD에는 Unionfs를 사용하여 놀라운 스택 파일 시스템을 구축할 수 있는 좋은 방법이 많이 있습니다. 현재 AWS에는 /usr 파일 시스템 트리의 대부분이 NFS를 통해 마운트되어 있기 때문에 1GB 디스크만 사용하는 시스템이 있습니다. 과거에는 기본 부팅에 /usr이 필요하지 않았기 때문에 이 작업을 쉽게 수행할 수 있었습니다. 이제는 조금 더 어려워졌지만(특히 부팅 실패 시 콘솔에서 나갈 수 없는 AWS에서는) 로컬 드라이브의 /usr 트리에서 필요한 최소값을 가져와서 관리했습니다. 가 나타나면 /usr 트리에 NFS를 마운트했습니다.

실행 중인 시스템에서 무언가를 업데이트해야 하는 경우를 대비해 기본 최소 로컬 하드 드라이브 /usr 트리에 계속 쓸 수 있는 백도어도 있습니다.

그것은 아름답습니다.

NFS(Amazon EFS)는 매우 느립니다. 그리고 버퍼 캐시가 제대로 작동하지 않습니다. 예를 들어, AWS 리소스 관리를 위한 aws 명령줄 인터페이스는 aws 명령이 호출될 때마다 많은 수의 포함을 흡수하는 Python을 사용합니다. 간단한 aws CLI 명령을 실행하는 데 20초가 걸립니다. 반복해서 실행해도 캐싱, NFS 속성 캐싱 등이 도움이 될 것 같지만 그렇지 않습니다.

가능한 해결책(FreeBSD에서):

그래서 제가 하고 싶은 일은 로컬 디스크 기반의 UFS 파일 시스템인 NFS 레이어 위에 또 다른 Unionfs 레이어를 놓는 것입니다. 그러나 부팅 시에는 비어 있으며 NFS에서 무엇이든 로드할 때마다(이제는 동적으로 업데이트되는 데이터가 아닌 안정적인 바이너리라고 가정) 디스크에 복사본을 남깁니다.

이 솔루션의 구현:

그래서 제가 생각하는 것은 다음과 같습니다. 존재하다/usr/src/sys/fs/unionfs/union_vnops.c다음과 같은 매우 간단한 코드가 있습니다.

static int
unionfs_open(struct vop_open_args *ap)
{
    ...
    if (targetvp == NULLVP) {
        if (uvp == NULLVP) {
            if ((ap->a_mode & FWRITE) && lvp->v_type == VREG) {
                error = unionfs_copyfile(unp,
                    !(ap->a_mode & O_TRUNC), cred, td);
                if (error != 0)
                    goto unionfs_open_abort;
                targetvp = uvp = unp->un_uppervp;
            } else
                targetvp = lvp;
        } else
            targetvp = uvp;
    }

(ap->a_mode & FWRITE)쓰기를 위해 하위 레이어에만 있는 파일에 액세스하면 이 부분이 상위 레이어에 복사됩니다 (uvp == NULLVP) && lvp->v_type == VREG.

모든 파일(읽기 전용 액세스 권한이 있는 파일도 포함)의 복사본을 생성하는 기능을 추가하는 것은 충분히 간단해 보입니다. 그런 다음 해당 복사본도 만들고 다음번에는 디스크에서 파일을 읽습니다.

이를 위해 새 옵션을 추가하겠습니다./usr/src/sys/fs/unionfs/union.h새로운 옵션인 복사 전략을 추가하겠습니다.

/* copy policy of upper layer */
typedef enum _unionfs_copypolicy {
       UNIONFS_COPY_ON_WRITE = 0,
       UNIONFS_COPY_ALWAYS
} unionfs_copypolicy;

struct unionfs_mount {
    struct vnode   *um_lowervp; /* VREFed once */
    struct vnode   *um_uppervp; /* VREFed once */
    struct vnode   *um_rootvp;  /* ROOT vnode */
    unionfs_copypolicy um_copypolicy;
    unionfs_copymode um_copymode;
    unionfs_whitemode um_whitemode;
    uid_t       um_uid;
    gid_t       um_gid;
    u_short     um_udir;
    u_short     um_ufile;
};

솔직히 말해서 저는 이러한 모든 패턴을 공간의 비트필드로 처리하고 싶습니다. 어쨌든, 이제 위의 코드를 다음과 같이 변경할 수 있습니다.

unp = VTOUNIONFS(ap->a_vp);
ump = MOUNTTOUNIONFSMOUNT(ap->a_vp->v_mount);
...

    if (targetvp == NULLVP) {
        if (uvp == NULLVP) {
            if (((ap->a_mode & FWRITE) || (ump->um_copypolicy == UNIONFS_COPY_ALWAYS)) && lvp->v_type == VREG) {
                error = unionfs_copyfile(unp,
                    !(ap->a_mode & O_TRUNC), cred, td);
                if (error != 0)
                    goto unionfs_open_abort;
                targetvp = uvp = unp->un_uppervp;

이것이 필요한 전부입니다. 즉, 속성과 섀도우 디렉터리의 모든 처리는 그래야 하는 대로 Unionfs_copyfile 함수 내에서 처리되기를 바랍니다.

이 경우 이제 커널 모듈 내부에 잘 위치한 mount_unionfs에 새로운 읽기 시 복사 정책 옵션을 추가하면 됩니다./usr/src/sys/fs/unionfs/union_vfsops.c

static int
unionfs_domount(struct mount *mp)
{
    int     error;
    ...
    u_short     ufile;
    unionfs_copypolicy copypolicy;
    unionfs_copymode copymode;
    unionfs_whitemode whitemode;
    ...
    ufile = 0;
    copypolicy = UNIONFS_COPY_ON_WRITE; /* default */
    copymode = UNIONFS_TRANSPARENT; /* default */
    whitemode = UNIONFS_WHITE_ALWAYS;
    ...
        if (vfs_getopt(mp->mnt_optnew, "copypolicy", (void **)&tmp,
            NULL) == 0) {
            if (tmp == NULL) {
                vfs_mount_error(mp, "Invalid copy policy");
                return (EINVAL);
            } else if (strcasecmp(tmp, "always") == 0)
                copypolicy = UNIONFS_COPY_ALWAYS;
            else if (strcasecmp(tmp, "onwrite") == 0)
                copypolicy = UNIONFS_COPY_ON_WRITE;
            else {
                vfs_mount_error(mp, "Invalid copy policy");
                return (EINVAL);
            }
        }

        if (vfs_getopt(mp->mnt_optnew, "copymode", (void **)&tmp,
            ...
        }
        if (vfs_getopt(mp->mnt_optnew, "whiteout", (void **)&tmp,
            ...
        }
    }
    ...

    UNIONFSDEBUG("unionfs_mount: uid=%d, gid=%d\n", uid, gid);
    UNIONFSDEBUG("unionfs_mount: udir=0%03o, ufile=0%03o\n", udir, ufile);
    UNIONFSDEBUG("unionfs_mount: copypolicy=%d, copymode=%d, whitemode=%d\n", copypolicy, copymode, whitemode);

따라서 이것은 FreeBSD에서 내가 원하는 것을 달성할 것입니다. 이제 내 시스템의 소스 코드를 가져와서 이 패치를 적용하고, Unionfs.ko 커널 모듈을 다시 컴파일하고 내 시스템으로 교체한 다음 작동할 수 있는지 확인해야 합니다.

# Custom /etc/fstab for FreeBSD VM images
/dev/gpt/rootfs  /        ufs      rw      1       1
/dev/gpt/varfs   /var     ufs      rw      1       1
fdesc            /dev/fd  fdescfs  rw      0       0
proc             /proc    procfs   rw      0       0
/usr             /.usr    nullfs   rw      0       0
fs-xxxxxxxx.efs.rrrr.amazonaws.com:/ /usr nfs rw,nfsv4,minorversion=1,oneopenown,rsize=1048576,wsize=1048576,hard,timeo=600,retrans=2,noresvport,late,bg 0 0
/var/cache/usr   /usr     unionfs rw,copypolicy=always 0 0

추가 개선 사항: 캐시 항목 제거

이제 나는 또 다른 미백 모드를 추가하고 싶을 수도 있다는 것을 깨달았습니다. 즉, 안 함입니다. 즉, 캐시에서 파일을 제거하는 효과를 사용하여 상위 레이어에서 파일을 제거할 수 있어야 하지만 하위 레이어에서 파일을 마스킹하는 화이트닝 효과는 없어 빈 것처럼 보입니다. Union.h에 UNIONFS_WHITE_NEVER를 추가하는 방법은 다음과 같습니다.

/* whiteout policy of upper layer */
typedef enum _unionfs_whitemode {
       UNIONFS_WHITE_ALWAYS = 0,
       UNIONFS_WHITE_WHENNEEDED,
       UNIONFS_WHITE_NEVER
} unionfs_whitemode;

그런 다음 Union_vnops.c에서:

static int
unionfs_remove(struct vop_remove_args *ap)
{
    ...
    if (uvp != NULLVP) {
        /*
         * XXX: if the vnode type is VSOCK, it will create whiteout
         *      after remove.
         */
        if (ump == NULL || ump->um_whitemode == UNIONFS_WHITE_ALWAYS ||
            (lvp != NULLVP && ump->um_whitemode != UNIONFS_WHITE_NEVER))
            cnp->cn_flags |= DOWHITEOUT;
        error = VOP_REMOVE(udvp, uvp, cnp);
    } else if (lvp != NULLVP && ump->um_whitemode != UNIONFS_WHITE_NEVER)
        error = unionfs_mkwhiteout(udvp, cnp, td, path);

그러면 rmdir에 관한 내용이 있을 수 있습니다.

static int
unionfs_rmdir(struct vop_rmdir_args *ap)
{
    ...
    if (uvp != NULLVP) {
        if (lvp != NULLVP) {
            error = unionfs_check_rmdir(ap->a_vp, cnp->cn_cred, td);
            if (error != 0)
                return (error);
        }
        ump = MOUNTTOUNIONFSMOUNT(ap->a_vp->v_mount);
        if (ump->um_whitemode == UNIONFS_WHITE_ALWAYS || 
            (lvp != NULLVP && ump->um_whitemode != UNIONFS_WHITE_NEVER))
            cnp->cn_flags |= DOWHITEOUT;
        error = unionfs_relookup_for_delete(ap->a_dvp, cnp, td);
        if (!error)
            error = VOP_RMDIR(udvp, uvp, cnp);
    }
    else if (lvp != NULLVP && ump->um_whitemode != UNIONFS_WHITE_NEVER)
        error = unionfs_mkwhiteout(udvp, cnp, td, unp->un_path);

이것은 또한 퇴거 작업을 수행해야 합니다.

하지만 그 모든 것을 하기 전에 사람들이 이미 알아낸 기존의 트릭이 있는지 궁금합니다.


추신: 여기 내 전체 차이점과 테스트 결과가 있습니다.https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=251363

짧은 대답은 다음과 같습니다. 실제로는 꽤 잘 작동합니다. 명확하지 않은 점이 하나 더 있습니다. Unionfs는 블록 장치를 사용하지 않지만 디렉토리를 사용합니다! 정말 멋지네요. 장치를 만들 필요도 없습니다. 제안한 대로 fstab을 업데이트했지만 나중에 NFS를 마운트할 때까지 지연해야 하기 때문에 전혀 사용하지 않을 것입니다. 따라서 이를 삭제하고 나중에 Unionfs 기반 캐시를 켜는 것이 좋습니다(예: /etc/rc.local에서). 이는 간단합니다.

mount -t unionfs -o copypolicy=always /var/cache/usr /usr

또한 /var/cache/usr 디렉토리가 여전히 직접 사용 가능하다는 것을 알았으므로 거기에서 파일을 삭제하면 캐시에서 제거됩니다! 즉, 미백 설정을 전혀 망칠 필요가 없습니다.

대신, Unionfs_copyfile(...) 호출이 "장치에 남은 공간이 없습니다"라는 오류를 반환하는 경우 캐시에서 이전 atime에 대한 파일을 제거하고 이전 파일을 제거할 때까지 이전 파일을 제거하는 자동 캐시 제거 전략을 생각해 내야 합니다. 공간이 회수된 후 다시 시도하세요. 이 작업입니다. 매우 간단합니다(오래된 atime 파일을 찾는 것을 제외하고).

가난한 사람의 단순 캐시 퇴거

며칠에 한 번씩 실행 하여 find /var/cache/usr -atime 2 -exec rm \{\}\;하루 동안 액세스하지 않은 항목을 제거하세요.

더 흥미로운 더 깊은 질문은 블록을 읽을 때 상위 레이어에 블록을 기록함으로써 Unionfs_copyfile(...) 함수를 더 효율적으로 만들 수 있는지 여부일 수 있습니다. 어쩌면 모든 것을 블록 지향적으로 만들 수도 있습니다. 그러면 하위 계층의 파일이 희박하면 상위 계층에서도 희박하게 유지됩니다.

답변1

NFS v3 또는 v4.x는 느리지 않습니다. 그래서 나는 당신이 NFS v2에 대해 이야기하고 있다고 가정합니다.

방금 매뉴얼 페이지를 탐색했습니다 man 5 nfs. 이 옵션을 우연히 발견했습니다금융위원회.

이것은 당신이 사용하고 싶은 일을하는 것 같습니다 cachefilesd. 아마도 /dev/shm에서 캐시를 찾을 수 있을 것입니다. 그러면 작업 속도가 더욱 빨라질 것입니다.

NFS를 통해 많은 동시 클라이언트에 CD를 제공하는 700MB RAM 캐시가 있었던 Solaris에서 이와 같은 작업을 수행한 것을 기억합니다.

관련 정보